URL: https://linuxfr.org/news/je-cree-mon-jeu-video-e04-paf-les-collisions Title: Je crée mon jeu vidéo E04 : Paf ! les collisions Authors: rewind palm123 et Benoît Sibaud Date: 2013年10月28日T15:46:05+01:00 License: CC By-SA Tags: gamedev Score: 46 «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 l'[épisode 03](http://linuxfr.org/news/je-cree-mon-jeu-video-e03-la-version-zero), on a vu la version zéro du jeu : un sprite qui bouge sur une carte. Les commentaires se sont focalisés sur la carte (la manière de l'afficher notamment) et sur le scénario/guide/design du jeu. Certains lecteurs ont même tenté la compilation (et y sont parvenus pour la plupart, malgré les difficultés). Aujourd'hui, on va parler de moteur physique, et plus particulièrement de Box2D, qui devient de plus en plus une référence en la matière dans les jeux 2D. ---- [Akagoria, la revanche de Kalista](https://gitorious.org/akagoria) [le tag gamedev](http://linuxfr.org/tags/gamedev/public) [Box2D](http://box2d.org/) ---- Un [moteur physique](http://fr.wikipedia.org/wiki/Moteur_physique) est une bibliothèque chargée de gérer les interactions physiques entre les entités du jeu. Techniquement, ça consiste à émuler des [forces qui s'appliquent à des solides](http://fr.wikipedia.org/wiki/M%C3%A9canique_du_solide). Par conséquent, un moteur physique ne s'occupe absolument pas de la manière dont s'affichent les entités. On peut donc choisir son moteur physique presque indépendamment de son moteur graphique. # Le moteur physique Box2D [Box2D](http://box2d.org/) est un moteur physique. C'est une référence en 2D, le moteur est utilisé dans plusieurs jeux, dont le très célèbre Angry Birds. La dernière version (2.2.1) est un peu vieille (septembre 2011) mais elle est assez complète. Voici donc un petit aperçu de comment marche Box2D. Ce n'est pas vraiment un tutoriel, c'est plutôt un point d'entrée pour aller voir plus loin. ## Bouge ton corps ! Un corps (_body_) est un élément [solide](http://fr.wikipedia.org/wiki/Mod%C3%A8le_du_solide_%28m%C3%A9canique%29#D.C3.A9finition), c'est-à-dire qu'il ne se déforme pas. Il est constitué de une ou plusieurs formes (_shape_), qui sont ajoutées au corps via des _fixtures_ auxquelles on associe des propriétés matérielles (densité, friction, restitution). Plusieurs corps peuvent être unis par des [liaisons mécaniques](http://fr.wikipedia.org/wiki/Liaison_m%C3%A9canique) (_joints_) qui vont les priver de [degré de liberté](http://fr.wikipedia.org/wiki/Degr%C3%A9_de_libert%C3%A9_%28m%C3%A9canique%29). Box2D permet de représenter toutes ces notions, voici quelques détails. Les formes peuvent être très variées, depuis le cercle jusqu'à n'importe quel polygone convexe. On peut aussi créer des chaînes (_chain_) qui sont des suites de segments, ouvertes ou fermées, qui seront infranchissables par les autres solides (et on imagine assez bien l'utilisation qu'on va pouvoir en faire dans un jeu). Comme dit précédemment, les _fixtures_ contiennent des propriétés matérielles. La [densité](http://fr.wikipedia.org/wiki/Densit%C3%A9) permet de calculer la masse du corps. La [friction (ou frottement)](http://fr.wikipedia.org/wiki/Frottement) permet de faire glisser deux corps l'un contre l'autre (pas d'allusion sexuelle dans cette phrase). La [restitution](http://fr.wikipedia.org/wiki/Coefficient_de_restitution) permet de gérer les rebonds entre deux corps. Une fixture peut également devenir un capteur (_sensor_), c'est-à-dire qu'il n'y aura pas d'interaction avec les autres corps mais qu'on pourra tester si un corps est en contact ou pas. Les corps peuvent être de trois sortes. Les corps statiques ne bougent pas, ils servent pour représenter des éléments fixes du décor et ne sont soumis à aucune force. Les corps dynamiques bougent, sont soumis à des forces et peuvent entrer en collision avec n'importe quel autre corps. Ils servent pour représenter les entités tels que les personnages. Une troisième catégorie de corps existe pour les corps qui ne doivent être soumis à aucune force mais qui doivent pouvoir bouger, comme une plateforme mobile par exemple. Ces corps sont des corps «cinématiques» (selon la terminologie de Box2D). Les corps statiques ou cinématiques ne peuvent pas entrer en collision les uns avec les autres. Un corps a une position dans le monde et un angle. On peut également endormir le corps si jamais on veut qu'il ne participe pas à la simulation (quand on a beaucoup de corps par exemple). On peut indiquer qu'un corps va avoir une grande vitesse (comme un projectile par exemple) et qu'il faudra faire attention lors de la simulation. Dans ces cas là, Box2D utilise une [détection de collision](http://fr.wikipedia.org/wiki/D%C3%A9tection_de_collision) continue de manière à éviter les effets tunnels, c'est-à-dire un corps qui passerait par dessus un autre sans le toucher. Enfin, il existe différents types de liaison mécaniques pour contraindre deux corps : liaison pivot, liaison glissière, etc. Elles permettent de modéliser des mécanismes tels que des poulies ou des tapis roulants ou plein d'autres choses de ce genre. ## Que la force soit avec vous ! Tous ces objets sont définis dans un objet monde (_world_) qui s'occupe de la gestion mémoire et de la simulation proprement dite. Cet objet monde est défini avec un vecteur gravité qu'on définira à 0 si jamais on veut simuler un plan horizontal, comme dans un jeu en vue de haut, et à un vecteur descendant si on simule un plan vertical, comme dans un jeu en vue de côté. Ou à un vecteur bizarre dans un [jeu où on s'amuse avec la gravité](http://devnewton.bci.im/fr/games/newton_adventure). Hormis la gravité, il est également possible d'appliquer des forces, soit linéaires, soit angulaires. Ou alors une [quantité de mouvement](http://fr.wikipedia.org/wiki/Quantit%C3%A9_de_mouvement) (_impulse_), soit linéaire, soit angulaire. Le corps se met alors en mouvement. C'est de cette manière qu'on peut faire bouger le héros ou les différents éléments mobiles d'un jeu. ## Pour aller plus loin Sur se former complètement sur Box2D, il y a d'abord la documentation officielle : * [la FAQ](https://code.google.com/p/box2d/wiki/FAQ), à lire avant de commencer ; * [le manuel en PDF](http://box2d.org/manual.pdf), qui décrit les concepts de manière simple et concise. Une fois le manuel lu, vous aurez envie d'aller plus loin et là, il existe également de la documentation très intéressante : * [Box2D tutorials](http://www.iforce2d.net/b2dtut/) : une mine d'or, il y a les rappels de base, mais aussi des cas concrets qui sont décrits et implémentés dans l'outil de test de Box2D ; * [The nature of code](http://vimeo.com/channels/natureofcode/60601614) : des tutoriels en vidéos, idéal pour ceux qui ne comprennent strictement rien aux paragraphes précédents. Et pour ceux qui veulent en savoir plus sur les moteurs physiques : * [How to create a custom 2D physics engine](http://gamedev.tutsplus.com/tutorials/implementation/create-custom-2d-physics-engine-aabb-circle-impulse-resolution/), des articles qui montrent la création d'un moteur physique complet ; * [Game physics](http://gafferongames.com/game-physics/), une série d'articles sur les bases mathématiques des moteurs physiques ; * [Game Physics Simulation](http://bulletphysics.org/wordpress/), le site de Bullet un moteur physique 3D. # Comment utiliser Box2D en pratique ## Une échelle adaptée Box2D requiert que les tailles des objets soient de l'ordre du mètre, la bibliothèque ne gère pas bien les très petits objets ou les très gros. Disons qu'entre une gemme et un dragon, ça passe. Il est donc nécessaire d'avoir une bonne échelle pour l'univers et de ne surtout pas utiliser des pixels comme unités. Une phase de conversion sera souvent nécessaire entre les coordonnées du monde Box2D et les coordonnées du monde du jeu ou celles de l'écran. ## Intégrer Box2D dans un système à entités J'ai repris l'exemple de [balles rebondissantes que j'avais présenté dans l'épisode 01](//linuxfr.org/news/je-cree-mon-jeu-video-e01-les-systemes-a-entites) pour ajouter la collision entre les balles à l'aide de Box2D. Le [résultat est disponible sur le dépôt github de `libes`](https://github.com/jube/libes/tree/master/examples/colliding_balls). Par rapport au [tutoriel](https://github.com/jube/libes/blob/master/doc/tutorial.md), voici les principaux changements liés à Box2D. Au niveau des composants, les `Position` et `Speed` ont été remplacés par un composant `Body` qui contient simplement un pointeur vers un `b2Body` de Box2D (tout ce qui commence par `b2`, c'est de l'API Box2D). ```cpp struct Body : public es::Component { b2Body *body; static const es::ComponentType type = 1; }; ``` Ensuite, il faut définir les murs et le sol. Pour cela, on utilise un corps statique (les constantes `WORLD_WIDTH` et `WORLD_HEIGHT` représentent la taille de la boîte dans laquelle les balles rebondissent). Ici, on dit que notre boîte fait trois mètres de haut pour 4 de large et on adapte l'échelle à la taille de la fenêtre. ```cpp // ground b2BodyDef groundBodyDef; groundBodyDef.position.Set(WORLD_WIDTH / 2.0f, -THICKNESS); b2Body* groundBody = m_world->CreateBody(&groundBodyDef); b2PolygonShape groundBox; groundBox.SetAsBox(WORLD_WIDTH / 2.0f + THICKNESS, THICKNESS); groundBody->CreateFixture(&groundBox, 0.0f); // right wall b2PolygonShape rightBox; b2Vec2 rightPos(WORLD_WIDTH / 2 + THICKNESS, 2 * WORLD_HEIGHT); rightBox.SetAsBox(THICKNESS, 2 * WORLD_HEIGHT + THICKNESS, rightPos, 0.0f); groundBody->CreateFixture(&rightBox, 0.0f); // left wall b2PolygonShape leftBox; b2Vec2 leftPos(- WORLD_WIDTH / 2 - THICKNESS, 2 * WORLD_HEIGHT); leftBox.SetAsBox(THICKNESS, 2 * WORLD_HEIGHT + THICKNESS, leftPos, 0.0f); groundBody->CreateFixture(&leftBox, 0.0f); ``` Reste ensuite à définir des balles. Une balle a un corps dynamique de sorte que les balles vont s'entrechoquer et vont rebondir contre les murs et sur le sol. ```cpp b2BodyDef bodyDef; bodyDef.type = b2_dynamicBody; bodyDef.position = pos; b2Body* body = world->CreateBody(&bodyDef); b2CircleShape circle; circle.m_radius = RADIUS / SCALE; b2FixtureDef fixtureDef; fixtureDef.shape = &circle; fixtureDef.density = 1.0f; fixtureDef.friction = 0.001f; fixtureDef.restitution = 0.98f; body->CreateFixture(&fixtureDef); ``` Et voilà, il n'y a plus qu'à lancer tout ça. Et les collisions se font automatiquement, il n'y a rien à faire, juste à afficher où les balles se situent. Ici, on ne gère pas l'angle des balles. Comme elles sont rondes, pas besoin de faire de rotation. Mais si on faisait la même chose avec des carrés, il faudrait afficher les carrés avec le bon angle. Il n'empêche que les balles tournent bien sur elles-mêmes et que ça a un impact (jeu de mot toussa) sur les collisions. J'oubliais presque. Pour simuler notre monde physique, il est nécessaire d'appeler la fonction adéquate, dans la mise à jour du système `Physics` : ```cpp int32 velocityIterations = 10; int32 positionIterations = 8; m_world->Step(delta, velocityIterations, positionIterations); ``` On voit qu'on peut régler le nombre d'itérations. Concrètement, pourquoi a-t-on besoin de plusieurs itérations ? Si on considère un [pendule de Newton](http://fr.wikipedia.org/wiki/Pendule_de_Newton), on se rend compte que la collision de la première bille va entraîner une force sur la seconde qui va elle-même entraîner une force sur la troisième, et ainsi de suite jusqu'à la dernière bille qui sera propulsée. Avec une seule itération, on ne pourrait gérer que la première interaction et non la suite d'interactions. Évidemment, il y a un compromis à trouver entre le nombre d'itérations et la précision : un grand nombre d'itérations donnera une meilleure précision mais prendra plus de temps et inversement. # Les autres nouvelles d'Akagoria ## Git branching model Sur les trois dépôts existants (`libes`, `libtmx` et `akagoria`), j'ai mis en place le fameux [successful git branching model](http://nvie.com/posts/a-successful-git-branching-model/), histoire de m'y retrouver moi-même dans ce que je fais. Il existe désormais une branche `develop` qui reçoit les nouveaux développements, et une branche `master` qui sera synchronisée quand les développements fonctionneront à peu près. En fait, ce modèle est très logique et fonctionne assez naturellement. L'essayer, c'est l'adopter ! ## Mise à jour libes : systèmes locaux La `libes` a reçu un nouveau développement qui permet de différencier des systèmes locaux et des systèmes globaux. Un système global est un système qui agit sur l'ensemble des entités qu'il gère, c'est ce qui était fait avant. Un système local est un système qui gère uniquement une partie des entités, celles qui sont autour du focus. Chaque système local traite donc un ensemble d'entités réparties dans une grille rectangulaire propre à chaque système. Au moment de mettre à jour les entités, le système détermine les cases de la grille qui sont concernées, celles où se trouve le focus ainsi que les 8 cases adjacentes, et les transmet à la fonction de mise à jour. Je ne sais pas si cette fonctionnalité est suffisamment intéressante pour se trouver ici ou si elle devrait être déportée dans le jeu lui-même. Pour l'instant, elle est ici. On pourrait encore l'optimiser. Pour l'instant, les entités concernées sont calculées à chaque fois, mais si le focus ne change pas, on pourrait renvoyer le dernier calcul fait, avec une [mémoïsation](http://fr.wikipedia.org/wiki/M%C3%A9mo%C3%AFsation). ## Mise à jour d'Akagoria : carte partielle Depuis le dernier épisode, je me suis concentré, entre autres, sur l'affichage de la carte (en mode moins bourrin qu'auparavant). J'ai utilisé pour cela les nouvelles fonctionnalités de `libes` décrites juste avant. J'ai donc créé un système `MapRender` qui est chargé du rendu de la carte. Il crée les entités correspondant à chacune des tuiles puis les place suivant une grille. La grille partage la carte en carrés de 50 tuiles de côtés, ce qui fait que sur ma carte actuelle, j'ai 25 cases en tout. Désormais, la carte n'est donc plus affichée entièrement. Quand on dézoome, on le constate, et quand on bouge, on le constate encore mieux, certaines zones disparaissent tandis que d'autres apparaissent. Pas de capture d'écran pour cette fois, mais la prochaine fois, il y aura des surprises ! ## Et toujours IRC Vous pouvez toujours passer sur le canal IRC #akagoria du réseau freenode pour poser des questions, avoir de l'aide pour la compilation du bouzin, ou juste pour dire bonjour. Il y a un compte connecté quasi en permanence qui me permettra de voir l'activité même quand je ne suis pas là.