Bruno Costa
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # Dwar - Documentation ![](https://i.imgur.com/Ns1qd1g.png) <div align="center">Fig.1 Image de titre avec la carte du jeu</div> <div align="center">Costa Bruno, Izzo Valentino et Lopes Da Silva Diogo - Grunenwald David & Beurret Stéphane</div> <div align="center">P2, HE-Arc, 24.01.2020</div> <div style="page-break-after: always;"></div> ## Résumé Cette documentation traite du parcours emprunté pour le développement d'un jeu fait en Qt. Chaque étape importante sera soulignée et détaillée afin de comprendre l'entièreté du projet. Le projet "Dwar" est un jeu vidéo où deux armées s'affrontent sur un plateau de jeu. Il y a l'armée du joueur qu'il peut composer lui-même avec au choix trois unités différentes qu'il peut acheter. Dans ces unités se trouvent un épéiste, un archer et un chevalier. La deuxième armée est contrôlée par une intelligence artificielle qui est composée en fonction du niveau de difficulté. Plus le niveau est élevé plus l'armée de l'IA est conséquente. Une option permet au joueur de créer en plus de la sienne, l'armée ennemie. Le but du jeu est de survivre et de battre toutes les unités contrôlées par l'IA. L'application est dans un état stable et respecte pratiquement le cahier des charges. Le joueur peut jouer une partie sans problème contre l'IA. Il y a quelques bugs mineurs connus qui sont corrigibles par la suite. De plus, une refonte du système de déplacement et d'attaque serait nécessaire. <div style="page-break-after: always;"></div> [TOC] <div style="page-break-after: always;"></div> ## 1. Introduction N'avez-vous jamais rêvé de contrôler votre armée de nains personnalisée ? Parce que nous, oui. > "Avec de la volonté, le rêve devient réalité" > --- > Walter, The Big Lebowski, 1998 Dans ce rapport, nous allons vous expliquer comment le jeu "Dwarf" a été analysé, conçu et implémenté. ### 1.1 Mise en contexte Ce projet a été réalisé dans le cadre du module de deuxième année, le P2. La durée du développement s'est déroulée sur le premier semestre de 2020-2021. Comme le langage principal de ce premier semestre est le Qt, l'application sera entièrement conçue avec le framework Qt de c++. ### 1.2 Le sujet du projet Le projet choisi est un jeu du type "Tactical RPG" où l'on suit, en général, l'histoire de personnages que l'on peut contrôler sous forme d'unités dans un champ de bataille. Comme le temps à disposition ne permet pas de créer un jeu en entier, le programme comprendra uniquement les phases de "gameplay". Pour se démarquer d'un jeu basique et comme le groupe est fan du "Seigneur des anneaux", il a été décidé que les unités seraient des nains. Ainsi, le jeu a été nommé "Dwar" &rarr; "Dwarf" & "War". ### 1.3 Les détails du jeu Le but du jeu est de créer son armée et de se battre contre une IA. Afin de défaire l'armée adverse, il faudra alors contrôler et mener à bien ses unités. Une fois que l'une ou l'autre des armées est anéantie, le jeu s'arrête et on peut alors choisir de recommencer ou de quitter l'application. ### 1.4 Cahier des charges En tant que propre client, le [cahier des charges](####Cahier-des-charges) a été rédigé par le groupe et validé par les enseignants responsables du module. #### 1.4.1 Objectifs du cahier des charges L'objectif principal du cahier des charges est d'apprendre à utiliser des interfaces graphiques modernes via le framework Qt au sein d'un projet. Quant aux objectifs du projet, ils sont mis en formes dans le [cahier des charges](####Cahier-des-charges) mis en annexe. ### 1.5 Le plan d'attaque Pour réaliser ce projet, nous passerons par une phase d'analyse complète du travail à faire, une conception pour savoir comment son architecture sera composée, ensuite ce sera une phase de planification pour finir sur l'implémentation du projet. ## 2. Analyse Lorsque l'on développe un projet informatique, il faut avant tout créer et poser une base solide qui est la structure du programme et son déroulement. Afin de construire cettedite base, une analyse détaillée du projet est donc nécessaire et c'est uniquement une fois fait que les informaticiens en charge de la création du logiciel pourront mettre en place une architecture et une logique propre et réfléchie. En effet, au commencement d'un projet, la première opération importante est de découper et de simplifier les différentes parties du programme en tâches distinctes et précises afin d'organiser une procédure à respecter. Il existe plusieurs méthodes pour réaliser cet objectif. Celle choisie par le groupe est la capture des besoins vue par l'utilisateur selon différents scénarii. ### 2.1 Discussions entre les membres du groupe La première étape était de mettre à jour la compréhension du projet au niveau du groupe, pour que chaque membre sache comment le jeu allait être et quelles seraient ses fonctionnalités. Un "brainstorming" général a alors été fait pour noter les différentes visions et idées sur le déroulement du programme. Comme Dwar est un jeu du genre "Tactical RPG", de multiples hypothèses ont donc été créées sur le suivi du programme en s'inspirant de la célèbre série de jeu vidéo "Fire Emblem", mais de manière simplifiée. Le projet n'étant pas un jeu à histoire, l'objectif était de créer un programme contenant uniquement le "gameplay" d'un "Tactical RPG", l'utilisateur devrait alors pouvoir choisir ses unités, les faire se battre contre des unités adverses et ainsi gagner ou perdre une bataille. ### 2.2 Création de croquis des interfaces graphiques Pour mieux se représenter les besoins de l'utilisateur quant à l'interface graphique, des croquis basiques ont été conçus. De ceux-ci en est ressortie l'idée principale que le logiciel devait avoir 5 "fenêtres". C'est après plusieurs réflexions que le groupe a au final décidé de n'en faire que 4 en remplaçant le menu de configuration du jeu par un dialogue à choix plus léger. 1. Menu principal 2. Menu du choix de l'armée ![](https://i.imgur.com/RGmSNsk.jpg) <div align="center">Fig.2 Shéma de l'interface du menu de création d'armée</div> 3. Plateau de jeu ![](https://i.imgur.com/ds3K367.png) <div align="center">Fig.3 Shéma de l'interface du plateau de jeu</div> 4. Menu de fin ### 2.3 Scénarii Pour but d'approfondir la recherche, quelques scénarii ont été simulés sur papier. Grâce à ceux-ci, quelques **nouvelles fonctionnalités** sont alors apparues. #### 2.3.1 Scénario I * L'utilisateur souhaite lire les règles * **Un pop-up s'ouvre et les règles sont affichées** * Il décide ensuite de lancer la partie * Un dialogue présente des choix de configuration * **L'utilisateur ne pourra jouer que contre une IA** * Il sélectionne le mode de jeu facile ou l'IA choisit son armée * Il choisit une dizaine d'unités * Il lance la partie donc contre un adversaire avec peu d'unités * Il se bat alors contre son ennemi et souhaite voir combien de points de vie il reste aux unités adverses * **Une interface apparaît et affiche les statistiques de l'unité sur l'écran** * Une fois l'équipe vaincue, un menu de fin apparaît * **L'utilisateur peut alors décider de : rejouer, retourner à la configuration d'armée ou quitter** * Il décide de quitter l'application #### 2.3.2 Scénario II * L'utilisateur lance directement la partie * **Il décide alors de choisir le mode où il choisit l'armée de l'IA** * Lors de la sélection d'armées, il se trompe d'unité * **Il la retire donc de son armée** * Il passe alors à l'armée de l'IA et lui donne une armée conséquente * À l'arrivée sur le plateau de jeu, il trouve que ses unités ne sont pas bien placées. * **Il déplace ses unités et les échange de place entre-elles** * Il perd alors contre l'IA, car son armée est trop forte * Il souhaite donc retourner à la configuration de l'armée pour donner à l'IA une armée raisonnable ### 2.4 Spécifications Une phase très importante de l'analyse est la spécification. C'est ici que le groupe va documenter tous les détails du projet et, de manière générale, ses fonctionnalités. #### 2.4.1 Unités Les différentes unités du jeu sont catégorisées en trois classes, elles ont chacune une manière de se battre et une portée de déplacement différente : * Saber (Fantassin ou Épéiste) * L'unité de corps-à-corps basique qui se bat à l'épée * Archer * L'unité à distance, elle utilise un arc * Rider (Cavalier) * L'unité lourde, elle peut se déplacer loin et résiste bien aux coups, mais ne fait pas beaucoup de dégâts #### 2.4.2 IA L'ennemi dans ce jeu est une IA qui contrôle son armée et le joueur peut choisir sa composition via sa difficulté. Cependant, ce n'est pas un paramètre qui va modifier sa façon d'agir. En effet, au départ, l'objectif était de rendre son comportement plus ou moins performant selon la configuration choisie, mais au cours du développement, il s'est avéré que les algorithmes utilisés ne pouvaient pas proposer des comportements différents selon la difficulté choisie. #### 2.4.3 Tâches bonus Le groupe avait également prévu des tâches dites "bonus", à faire si le temps le permet. Le choix s'est porté sur l'ajout d'objet achetable lors de la sélection de l'armée ou des pouvoirs spéciaux selon l'unité à utiliser durant la bataille. ### 2.5 Solution retenue À la fin de l'analyse, le jeu prenait cette forme : * L'utilisateur commence par arriver sur un menu principal où il peut : * Jouer * Lire les règles * Quitter le programme * Si l'utilisateur décide de jouer, un dialogue apparaît et il peut configurer sa partie. * S’il veut choisir l'armée ennemie, il suffit de décocher la case * "L'IA sélectionne son armée" * Sinon, il peut choisir un mode de difficulté qui va changer la composition de l'armée ennemie. * Il arrive ensuite sur un menu où il peut créer son armée. * Il peut choisir d'ajouter des unités comprises entre les 3 classes précédemment citées. * S’il a décidé de faire en sorte qu'il choisisse l'armée de l'IA, il peut ensuite sélectionner les différentes unités de l'ennemie. * Une fois choisie, il arrive sur un plateau de jeu où sont disposées ses unités et celles de l'ennemi * Il peut alors préparer ses unités pour le combat en les déplaçant où il souhaite * Il peut également déplacer les unités ennemies dans les différents "spawns" * Lorsqu'il est prêt, il peut lancer la bataille. * Il maîtrise ensuite ses unités et va les faire se battre contre les ennemis présents * Dès qu'une des deux armées est anéantie, c'est la fin de la partie ## 3. Conception La conception est la suite de l'analyse, c'est ici que nous allons spécifier le travail à venir. Effectivement, un passage sur les parties créées lors de l'analyse sera fait et a pour but de mieux les définir. Une maquette pour une interface graphique, un schéma de classe pour une fonctionnalité, tout est revisité pour avoir un plan d'attaque concret et simple d'exécution. C'est également ici que les premiers détails liés au code, comme par exemple quel langage/framework ou encore les bibliothèques employées, sont définis. ### 3.1 Langages et/ou frameworks utilisés Le framework demandé et utilisé est Qt : un framework de c++ développé pour créer des interfaces graphiques adaptatives. #### 3.1.1 Bibliothèques ou outils externes Les outils externes utilisés sont [Tiled Map Editor](####[9]-Tiled,-mapeditor.org/) afin de créer le plateau de jeu ainsi que GIMP2 ou encore paint.net pour ce qui est du design. La bibliothèque XML installée avec Qt a également été utilisée. ### 3.2 Diagramme UML L'architecture du programme est la partie la plus importante de la conception, c'est ici que les premières bases de code se mettent en place. Nous avons donc créé un [diagramme UML](####-Diagramme-UML-sur-lucidchart) représentant la structure générale du logiciel. Ce diagramme est séparé en deux parties, la première est l'ensemble des menus et fenêtres du jeu et la seconde, quant à elle, regroupe les éléments graphiques comme les unités, les cases de jeu ainsi que le plateau de jeu en lui-même. #### 3.2.1 Partie menus - à gauche Dans cette partie du [schéma UML mis en annexe](####-Diagramme-UML-sur-lucidchart), sont présents les différents menus du jeu d'une manière un peu spéciale. L'élément principal est le MainWidget qui est la fenêtre principale du jeu, elle se situe au centre du diagramme et toutes les autres classes y sont reliées. Effectivement, chacune des classes représente un menu, elles ont donc un rôle respectif bien distinct. ##### 3.2.1.1 Menus 1. MainMenu * Le menu principal, c'est ici que le jeu se lance. 2. SettingsMenu * Le dialogue de configuration. 3. ArmySelection * Le menu de sélection d'armées, où l'on choisit l'armada. 4. EndMenu * Le menu de fin. Le MainWidget possède un QStackedLayout qui va recevoir comme éléments les différents menus pour ensuite les changer d'ordre selon le besoin. #### 3.2.2 Partie éléments - à droite Dans la seconde partie se trouvent les éléments graphiques et la fenêtre de jeu : en somme, tous les éléments liés au "gameplay". Le plus important d'entre eux est la BattleWindow, c'est l'interface qui contient tous les autres éléments comme la carte, les cellules ou encore les unités. C'est également dans celle-ci que nous pourrons jouer au jeu, déplacer et attaquer des unités. Les autres éléments sont les classes des joueurs, des unités et celle de la carte qui contient des cellules. ##### 3.2.2.1 Unités La classe Unit possède trois enfants qui sont les différentes catégories existantes et hérite de QGraphicsItem. Une unité peut attaquer d'autres unités et se déplacer. ##### 3.2.2.2 Joueur La classe Player est la classe parente de la classe Human et AI. C'est eux qui vont posséder les différentes unités et l'argent nécessaire à leur achat. De plus, la classe AI comprend plusieurs méthodes utiles à son comportement. ##### 3.2.2.3 Cellule et carte La classe Map contient une liste de cellules, qui héritent de QGraphicsItem, correspondant au tableau de jeu. Elle hérite de QGraphicsView et va afficher sous forme d'échiquier toutes les cellules du plateau et toutes les unités. ## 4. Planification La planification est la dernière étape avant le début de l'implémentation, c'est ici que les tâches sont ordonnées par ordre d'importance et mise dans un échéancier tel qu'un diagramme de GANTT et/ou de PERT. Le groupe a alors utilisé un diagramme de GANTT et les systèmes mis à disposition sur gitlab pour mettre en place la planification du projet. Pour ce qui est de la communication, l'utilisation de Discord a été préférée à celle de Teams. ### 4.1 GANTT, Gitlab & Discord Le GANTT a été un des premiers documents qui a été préparé. En effet, il nous permet d'organiser les différentes opérations à faire et de nous rendre compte de la taille de notre projet et du nombre de tâches qui doivent être réalisées. De plus, nous pouvons y entrer un suivi des heures passées sur les différentes tâches du projet. Sur la forge de l'école, les fonctionnalités des "issues", "boards" et "milestones" ont été exploitées. Grâce à celles-ci, nous avons pu mettre en évidence les tâches finies, en cours et à faire de manière ergonomique. Puisque nous voulions un projet centré en un endroit, l'emploi du Wiki de gitlab aura été utilisé pour rédiger la documentation, les journaux de travail ainsi que les différents documents liés au projet. Finalement, Discord était et est toujours l'outil de communication préféré du groupe. Un serveur a donc été créé pour accueillir nos conversations et les différents partages de fichiers. ### 4.2 Retard Au cours du projet, la crise COVID-19 déjà présente s'est empirée et nous avons alors dû nous adapter à un environnement de travail inhabituel, ce qui a impacté notre travail. Nonobstant le challenge présenté par ce changement, nous avons essayé de gérer la charge de travail engendrée du mieux possible. Cependant, du retard s'est tout de même accumulé ce qui a créé un effet boule de neige. Ce problème a été partiellement géré en augmentant le rythme de travail que nous avions précédemment. De par ce fait, certaines tâches n'ont pas été réalisées à temps. ### 4.3 Améliorations du planning Cependant, c'est après une analyse du planning que nous avons trouvé plusieurs facteurs permettant d'améliorer une future gestion de projet : * Meilleure analyse des tâches à effectuer et du temps qu'elles demandent pour les finir * Meilleure gestion des priorités * Meilleure répartition des tâches * Apporter un soutien plus grand aux membres du groupe en difficulté Des tâches bonus citées précédemment n'ont pas pu être faites et sont déjà de bonnes voies pour améliorer le projet. Ce qui a également été appris c'est que la planification et la création d'un diagramme GANTT sont une phase très importante du projet et elles permettent d'économiser beaucoup de temps. Finalement, nous avons surtout gagné de l'expérience et de futurs projets sur Qt seront désormais beaucoup moins "inconnus". ## 5. Réalisation La réalisation est la phase centrale d'un projet, c'est aussi celle qui va occuper le plus de temps. Ici, le sujet est l'implémentation des éléments importants du programme ainsi que leurs explications détaillées. ### 5.1 Widget principal Le widget principal soutient tous les autres widgets, c'est lui qui va échanger les fenêtres entre-elles lorsque les conditions sont respectées. Il contient un QStackedLayout qui empile les différentes interfaces utiles au jeu, de plus il va remonter les widgets appelés par certains signaux à la surface pour les afficher. #### 5.1.1 Refresh Lorsque le joueur souhaite rejouer ou retourner à la configuration de l'armée, le widget principal doit "refresh" ou "renouveler" les fenêtres générées à l'exécution du programme. Pour ce faire, une fonction qui va recréer ces fenêtres comme neuves a été implémentée. Cependant, ne trouvant pas d'alternative propre, nous avons opté pour un code "smell" qui fonctionne très bien. ```cpp= void MainWidget::refreshWidgets(){ mainLayout->removeWidget(this->armySelectionMenu); mainLayout->removeWidget(this->battleWindow); // if the object exist, then we need to erase it if(this->armySelectionMenu != nullptr && this->battleWindow != nullptr){ delete this->armySelectionMenu; delete this->battleWindow; } this->armySelectionMenu = new ArmySelectionMenu(this); this->battleWindow = new BattleWindow(this); mainLayout->insertWidget(1, this->armySelectionMenu); // index 1 mainLayout->insertWidget(2, this->battleWindow); // index 2 QObject::connect(this->armySelectionMenu, &ArmySelectionMenu::armyChoosen, this, &MainWidget::moveToMap); QObject::connect(this->battleWindow, &BattleWindow::winner, this, &MainWidget::moveToEndMenu); } ``` Ce code va détruire les interfaces que nous avons besoin de réutiliser et va les recréer. ### 5.2 Sélection de l'armée C'est ici que le joueur va pouvoir composer son armée et/ou celle de l'IA. Dans ce chapitre sont expliqués les comportements des différents boutons qui permettent l'ajout ou la suppression d'unité dans les armées et la validation des deux équipes. De plus, l'élément principal de ce menu, les unités, est détaillé. #### 5.2.1 Unités Les unités héritent de QGraphicsItem, ce qui signifie qu'elles sont définies par un boundingRect et une fonction paint. Le boundingRect est un QRectF instancié par copie de la cellule sur laquelle se trouve l'unité. La fonction paint quant à elle, dessine le sprite QPixmap de l'unité et quand elle est sélectionnée, dessine aussi son boundingRect en jaune. Le clic sur l'unité est géré par la map. #### 5.2.2 Bouton pour valider l'armée On ne peut cliquer sur ce bouton que si au moins une unité est dans l'armée. Si plus tôt on a choisi que l'IA choisisse son armée alors quand on clique sur le bouton on quitte la sélection d'armées pour le plateau de jeu. Si on n’a pas coché la case alors lorsque l'on clique sur le bouton on devra choisir manuellement l'armée de l'IA. ### 5.3 Création de la carte et utilisation de Tiled Tiled est un logiciel gratuit et libre, qui permet de créer des niveaux ou des cartes de jeu en 2D. Chaque carte est composée de cases qui sont nommées tuile dans le logiciel. Chaque tuile est d'une taille standard de 32x32 ou 48x48 pixels. Ensuite, on peut venir remplir chaque tuile avec des images de la même taille pour donner vie à notre carte et la customiser à notre goût. Ces petites tuiles sont stockées dans des images plus grandes qu'on appelle Tileset ou jeux de tuiles en français. On peut créer nos propres tileset ou alors utiliser ceux libres de droits de la communauté. Pour simplifier le design, il existe des calques de tuiles, qui sont des couches où on peut ajouter des tuiles. On peut aussi ajouter des calques d'objet sur la carte qui permet de sélectionner des tuiles et leur donner un type et un nom ou même des propriétés personnalisées. Quand on a terminé de créer notre carte on peut l'exporter en png et le format de base est un .tmx qui est le type d'extension de Tiled. Dans ce fichier tmx on retrouve du XML qui décrit les informations des claques et les propriétés de la carte. #### 5.3.1 Notre carte Tiled La carte du jeu forme un carré de 25 par 25 tuiles. Les tuiles ont une taille de 32 par 32 pixels. Le plus important sur cette carte sont les calques d'objets. Il y a un calque "spawn" qui va contenir toutes les cases où les unités pourront apparaître dessus. Pour les spawns des alliés, le type du calque est "41" et pour l’ennemi "42". Il y a aussi le calque "stop" avec le type "1". On applique ce calque aux tuiles où il ne faut pas qu’une unité se déplace dessus. Le calque "forest" de type "2" contient les cases où se trouve un arbre. Pour finir le calque "mountain" de type "3" qui est pour les tuiles avec une montagne dessus. Les calques "forest" et "mountain" sont importants, car ces tuiles donnent un comportement différent pour l'unité. Dans le cas où un personnage se trouve sur l'une de ces cases, son déplacement est plus long. Il faut deux points de déplacement pour pouvoir passer sur cette tuile. Cependant, dû au problème de planification annoncé ci-dessus, l'ajout du comportement spécifique lié au type "2" et "3" n'a pas pu être fait. Les Tileset utilisés sont ceux de la communauté par [pipoya](####[1]-FREE-RPG-Tileset-32x32,-Pipoya,-pipoya.itch.io/pipoya-rpg-tileset-32x32). ![](https://i.imgur.com/lU0DrP6.png) <div align="center">Fig.4 La carte du jeu</div> #### 5.3.2 Cell La classe Cell dérivée de QGraphicsItem va stocker les informations d'une cellule. Elle contient son id, le type, son état et les dimensions de celle-ci. La cellule peut aussi se dessiner avec la méthode "paint" de QGraphicsItem. En effet, de base, la cellule dessine son cadre en noir grâce à ses dimensions. Cependant quand on est au début du jeu, les cases où les unités peuvent spawns doivent être visibles. Alors c'est là que le type rentre en jeu. Si, il est égal à "41", la cellule doit être de couleur bleue pour les spawns alliés. Dans le cas où le type est de "42", c'est du rouge qui va être dessiné autour de la case, pour les spawns ennemies. ```cpp= if(type2 == 41){ painter->setPen(Qt::blue); } else if(type2 == 42) { painter->setPen(Qt::red); } ``` L'état de la cellule est utile pour montrer à l'utilisateur que cette cellule permet une interaction avec une unité. L'état "1" montre qu'une unité sélectionnée peut se déplacer sur cette case aux contours verts. L'état "2" s'affiche par des contours rouges si cette case fait partie de la portée d'attaque de l'unité active. ```cpp= if(state == 1){ painter->setPen(Qt::green); } else if(state == 2) { painter->setPen(Qt::red); } ``` #### 5.3.3 Map La classe Map va permettre d'afficher la carte, les unités et de contenir les informations des cases qui forment le plateau de jeu. La Map est dérivée de la QGraphicsView. Dans cette partie, le traitement du XML est nécessaire pour pouvoir lire le fichier "dwarMap.tmx". Donc il faut prévenir le compilateur qu'on va utiliser la bibliothèque XML. Pour ce faire, dans le fichier .pro, l'ajout de la ligne suivant est obligatoire. ```= QT += xml ``` Dans la classe Map, l'inclusion de QtXml est nécessaire pour l'utilisation du module XML. ```cpp= #include <QtXml> ``` La map va récupérer le clic, vérifier tous les items de la scène qui sont à cet endroit, comparer le type du QGraphicsItem et déterminer si c'est une unité ou une cellule et émettre le signal correspondant. ##### 5.3.3.1 LoadFile La méthode loadFile est la méthode qui va permettre de charger le fichier "mapDwar.tmx". Pour pouvoir lire les informations du fichier plus tard. Si le fichier n'a pu être lu, une erreur est affichée. Dans le cas où le fichier a bien été lu, le contenu de ce document est sauvegardé dans la variable xmlDom qui représente le contenu XML, qui est du type QDomDocument qui fait partie de ces fonctionnalités QtXml. ##### 5.3.3.2 MakeCells Cette méthode makeCells va créer la liste de Cells qui forment la carte du jeu. Chaque cellule va être créée avec un id qui est incrémenté après l'ajout d'une nouvelle cellule, son type qui est de base à "0", la position x et y et la longueur et largeur. Les valeurs de dimensions sont récupérées par [readTmx](#####5.3.3.3-ReadTMX). Ce qui permet d'avoir une compatibilité avec d'autres tailles de carte. Quand la liste a fini d'être créée, la méthode appelle [addCells](#####5.3.3.3-Ajout-des-items) qui ajoute les cellules dans la scène de la Map. ##### 5.3.3.3 ReadTMX La méthode readTMX va se déplacer dans le document XML, pour récupérer les informations de taille de la carte et les détails de toutes les cellules. En premier on va placer dans une variable locale QDomElement map, le document XML récupéré dans la méthode [loadFile](#####5.3.3.1-LoadFile). Au début la variable map est placée sur le premier élément du fichier XML. Dans cet élément qui s'appelle "map", on y retrouve plusieurs informations basiques sur la carte. Ce qui est intéressant c'est de récupérer le nombre de tuiles en longueur et largeur. Ensuite, la largeur et longueur en pixels des tuiles. Dans notre cas la carte fait du 25x25 tuiles et chaque tuile est de 32x32 pixels. ```xml= <map version="1.4" tiledversion="1.4.2" orientation="orthogonal" renderorder="left-up" width="25" height="25" tilewidth="32" tileheight="32" infinite="0" nextlayerid="15" nextobjectid="505"> ``` Ces valeurs sont intéressantes à récupérer dans le cas où la carte change de taille, car on change de niveau par exemple. Ce qui évite de revenir dans le code et de devoir modifier à la main ces valeurs. ```cpp= QDomElement map = this->xmlBOM.documentElement(); //Recupere les infomations principals de la map this->nbTilesWidth = map.attribute("width").toInt(); this->nbTilesheight = map.attribute("height").toInt(); this->tilesWidth = map.attribute("tilewidth").toInt(); this->tilesHeight = map.attribute("tileheight").toInt(); ``` Ensuite, dans la méthode, on peut maintenant appeler [makeCells](#####5.3.3.2-MakeCells), car on dispose des valeurs nécessaires à la création de la liste de cellules. Dans le fichier XML se trouvent plusieurs éléments "objectgroup" qui sont les calques d'objets. Dans ces groupes d'objets se trouvent tous les objets qui font partie du calque. Ce sont les informations nécessaires, qu'il faut ajouter à notre liste de cellules. La donnée importante à récupérer est le type de la cellule, ce qui permet de savoir si c'est une montagne, une forêt et/ou une case de spawn. Les autres valeurs vont pouvoir aider à savoir quelle cellule doit recevoir une modification du type. ```xml= <objectgroup id="10" name="stop" visible="0"> <object id="364" type="1" x="416" y="288" width="32" height="32"/> <object id="365" type="1" x="448" y="288" width="32" height="32"/> ... </objectgroup> <objectgroup id="14" name="spawn" visible="0"> <object id="431" type="41" x="96" y="0" width="32" height="32"/> <object id="432" type="41" x="128" y="0" width="32" height="32"/> ... </objectgroup> <objectgroup id="11" name="forest" visible="0"> <object id="124" type="2" x="0" y="0" width="32" height="32"/> <object id="125" type="2" x="32" y="0" width="32" height="32"/> ... </objectgroup> <objectgroup id="12" name="mountain" visible="0"> <object id="151" type="3" x="64" y="160" width="32" height="32"/> <object id="152" type="3" x="96" y="160" width="32" height="32"/> ... </objectgroup> ``` Pour accéder à ces groupes d'objets, il suffit de se déplacer dans le document en spécifiant le nom "objectsgroup". Ce qui est intéressant c'est de récupérer tous les "objectgroup" en une liste d'éléments et c'est possible avec le type QDomNodeList. ```cpp= QDomNodeList nodes = map.elementsByTagName("objectgroup"); ``` Ensuite on va parcourir cette liste et aller plus en profondeur en récupérant une liste d'"object". Après avoir pris possession de tous les objets, il faut parcourir notre liste de cellules. À chaque cellule on va vérifier si les coordonnées de l'object correspondent à celles de la case. Si c'est le cas, on va venir appliquer le nouveau type. Sinon on continue le parcours des cellules. ```cpp= for(int i = 0; i < nodes.count(); i++) { QDomNodeList objects = nodes.at(i).toElement().elementsByTagName("object"); QDomElement object; int nb = 0; //Parcours tous le tableau de cellules for(int j = 0; j < listCells.length(); j++){ object = objects.at(nb).toElement(); int oX = object.attribute("x").toInt(); int oY = object.attribute("y").toInt(); int x = listCells.at(j)->boundingRect().x(); int y = listCells.at(j)->boundingRect().y(); //Controle que la cellule est la meme que l'objet if(x == oX && y == oY) { listCells[j]->setType(object.attribute("type").toInt()); nb++; } } } ``` ##### 5.3.3.3 Ajout des items Maintenant que la Map est bien configurée, il faut y ajouter du contenu dans sa scène. En premier est ajoutée la carte au format png. Ensuite on a une méthode addCells qui est appelée par [makeCells](#####5.3.3.2-MakeCells) quand elle a terminé de créer les cellules. Cette méthode va ajouter à la scène les cases une par une. Sur cette map il ne reste plus que les unités à afficher et c'est la méthode addUnits qui va le faire. Elle prend en arguments deux listes, l'armée de l'allié et celle de l'ennemie. Finalement, l'ajout des unités dans la scène se fait avec un simple parcours des deux listes. ##### 5.3.3.4 Gestion du clic sur la Map Si l'utilisateur durant la partie clique sur la Map, cela va être détecté par la méthode mousePressEvent. Dans cette méthode, il est nécessaire de capturer les clics sur une unité ou sur une cellule de la carte. En premier on va contrôler que l'appui soit bien sur un item de la Map. Pour ce faire, la méthode utilisée est itemAt qui prend en argument la position de la souris au moment du clic. Si c'est le cas, il est possible qu'à cet endroit se trouve une unité, mais sur une case. Alors, il faut récupérer la liste des items qui répond à ce clic. Maintenant, il faut faire un parcours double de la liste, car il faut contrôler si le type de l'item correspond soit à une Unit ou une Cell. Ensuite il faut renvoyer à la BattleWindow l'unité cliquée avec emitUnit ou la cellule avec emitCell. ### 5.4 Battle Window Battle Window est le widget central de l'application, c'est là où le combat entre l'armée alliée et ennemie se produit. De plus, le code des différents contrôles utiles au jeu y est implémenté. #### 5.4.1 Algorithme de portée Pour cet algorithme, la difficulté est qu'il faut tester tous les chemins pour vérifier qu'il y en ait au moins un qui fonctionne. Ici est imagé l'algorithme avec les déplacements d'un archer (2 de déplacement). Le fonctionnement est le suivant : L'algorithme récupère l'index de la cellule où se trouve l'unité puis effectue une première boucle qui va représenter le fait d'avancer à droite dans un premier temps puis à gauche dans un second. Cette boucle restera sur l'axe X, elle permet de se déplacer sur chaque colonne. ![](https://i.imgur.com/VS52bC7.png) <div align="center">Fig.5 Schéma algorithme de portée 1</div> Dans cette boucle il y en a une autre qui va faire le même travail, mais vers le haut et le bas. Cette fois celle-ci va permettre d'aller sur chaque ligne. ![](https://i.imgur.com/2rUuXUE.png) <div align="center">Fig.6 Schéma algorithme de portée 2</div> On pourrait se dire que c'est suffisant, car on regarde toutes les cases, mais le problème c'est qu'il pourrait y avoir des obstacles. Il faut donc sortir de la boucle, car on ne peut pas continuer dans cette direction, ce qui fait qu'il va y avoir des cases auxquelles on n'a pas accès alors qu'on pourrait y arriver en contournant l'obstacle. Voici l'image avec, partiellement, le dessin d'une unité qui a 4 de déplacement. Chaque point rouge représente l'endroit ou une boucle démarre. ![](https://i.imgur.com/8K0yALU.png) <div align="center">Fig.7 Schéma algorithme de portée 3</div> Pour résoudre ce souci, l'algorithme continue avec deux boucles supplémentaires. La première va donner la direction à vérifier, au début à gauche, puis à droite. La seconde, quant à elle, va récupérer les cellules dans la direction donnée par la première boucle afin de tester si le déplacement est possible. ![](https://i.imgur.com/YCaK864.png) <div align="center">Fig.8 Schéma algorithme de portée 4</div> Si on reprend l'exemple précédent, voici comment l'algorithme vérifie les cases. ![](https://i.imgur.com/hEnnALy.png) <div align="center">Fig.9 Schéma algorithme de portée 5</div> On voit bien que la case au-dessus de l'obstacle, qui n'était pas atteignable auparavant, l'est. Maintenant le problème est qu'on vérifie plusieurs fois les mêmes cases et donc on perd des ressources. Pour résoudre ce problème l'algorithme regarde avant si cette case a déjà été testée, si c'est le cas alors elle sort de la boucle, car si celle-ci est vérifiée, alors celles d'après le sont aussi. #### 5.4.2 Algorithme de pathfinding L'algorithme de pathfinding qui a été choisi est le très célèbre A* (prononcé A-Star), il est utilisé pour préciser les déplacements des unités. En effet, comme l'algorithme de déplacement est suffisamment bien fait, il peut déplacer toutes les unités, autant alliées que celles contrôlées par l'IA, de manière correcte. Ainsi, le pathfinding n'est pas utilisé pour **déplacer**, mais pour assurer que l'unité est passée par le bon chemin lors de son déplacement. ##### 5.4.2.1 Fonctionnement L'A* se comporte comme un humain perdu qui essaie de trouver un chemin dans un labyrinthe. Il va partir de son point de départ et tester chaque possibilité et décide de la garder ou de la mettre de côté selon son exactitude. Cependant, une option mise de côté n'est pas supprimée, il se peut que l'algorithme passe par un endroit qui, au début, lui paraît intéressant pour au final arrivé dans un cul-de-sac. Il doit alors rebrousser chemin et réétudier les cas mis de côté pour estimer une nouvelle possibilité. Pour étudier les possibilités, il faut récupérer des distances entre les points à proximité de là où on se trouve, les points qui font potentiellement partie du chemin, et le point de destination. Afin de calculer cette distance, il faut utiliser une heuristique, il en existe plusieurs, mais celle qui a été choisie est l'heuristique de la distance de manhattan qui consiste à mesurer la distance entre deux points via Pythagore. ###### 5.4.2.1.1 Listes ouverte et fermée L'algorithme possède deux listes qui permettent d'étudier les différentes cellules existantes et pouvant faire partie du chemin final. 1. La liste ouverte La liste ouverte contient toutes les cellules dignes d'être étudiées. On y ajoute chaque nouvelle cellule rencontrée qui est libre et juste avant de l'ajouter on étudie les différentes valeurs indiquant son importance. 2. La liste fermée La liste fermée quant à elle contient uniquement les cellules dont on est sûr qu'elles feront partie du chemin final. C'est au final, la solution retenue de l'algorithme. ###### 5.4.2.1.2 Représenter une cellule dans les listes Pour pouvoir étudier chaque cellule, il faut des valeurs types qui indiquent l'importance et l'exactitude au sein de la liste. C'est pourquoi une cellule possède 3 "coûts" : * Le coût G (la distance entre le point de départ et le point étudié) * Le coût H (la distance entre le point étudié et le point de destination) * Le coût F (la somme du coût G et du coût H) Plus le coût F est bas, plus la cellule est considérée comme bonne. Finalement, afin de pouvoir lier les cellules entre-elles pour former un chemin, une notion de parent est utile (Le parent étant la cellule précédant celle étudiée). Dans le cas de ce projet, l'id de la cellule sera utilisée pour nommer un parent. (Comme le point de départ n'a pas de parent, son id est à -1). ###### 5.4.2.1.3 Éligibilité d'une cellule Pour qu'une cellule soit étudiée, il y a plusieurs critères : * Si elle est occupée ou si c'est un obstacle : on oublie la cellule * Si elle est déjà dans la liste fermée : elle a déjà été étudiée, on oublie la cellule * Si elle est déjà dans la liste ouverte : on recalcule les coûts et on compare avec son homologue, si elle est meilleure, on la remplace. Cela veut dire que le chemin se met à jour. * Sinon, on l'ajoute dans la liste ouverte et on calcule son importance. ###### 5.4.2.1.4 Déroulement L'A* cherche son chemin en commençant par le point de départ, il vérifie ses voisins adjacents et juge leur qualité pour ensuite les ajouter dans la liste ouverte. Ensuite, il récupère le meilleur élément de la liste ouverte, l'ajoute dans la liste fermée et répète le procédé à partir de celui-ci, ainsi de suite jusqu'à arriver à destination. S’il n'arrive jamais à la destination, c'est qu'il n'existe aucune solution. ##### 5.4.2.2 Code C'est dans cette section que l'implémentation de l'A* va être expliquée en détail ###### 5.4.2.2.1 Structure de données Afin de représenter un "noeud" du chemin, il a été décidé d'utiliser une structure représentant un noeud, une cellule, qui contient alors les 3 coûts énoncés plus tôt et l'id de son parent. Si c'est la première node de son chemin, l'id de son parent sera mis à -1. ```cpp= // node used to resolve the pathfinding algorithm struct node{ // costG => cost to go from the start node to the studied node // costH => cost to go from the studied node to the end node // costF => the sum of the two above double costG, costH, costF; int parentID; }; ``` Pour les deux listes, ouvertes et fermées, des maps ont été utilisées. En effet, comme nous souhaitons avoir une unicité au sein des nodes, l'emploi des maps était plus que logique puisqu'elles ne permettent aucun doublon. La paire utilisée pour stocker chaque node est : * Clé : int - l'id de la node * Valeur : node - struct établie plus haut. ```cpp= // typedef to facilite the use of map<int, node> typedef std::map<int, node> listNode; ``` ###### 5.4.2.2.2 Fonctions utiles Pour que le code fonctionne, il a besoin de deux fonctions basiques qui permettent de calculer la distance entre deux cellules (distance de manhattan) et une autre pour vérifier si une node est présente dans une des listes ou non. De plus, une fonction permettant de récupérer la meilleure valeur d'une liste ainsi qu'une autre qui ajoute un élément à la liste fermée sont nécessaires. ###### 5.4.2.2.3 Ajout des cellules adjacentes à la cellule étudiée C'est la fonction la plus importante, elle va vérifier si les cellules adjacentes sont utilisables, si oui elle va les étudier et les ajouter ou non aux listes correspondantes. Au final, c'est un peu le mur porteur de l'algorithme. Il fonctionne ainsi : * On récupère la cellule de départ * On vérifie les cellules adjacentes et si elles sont utilisables * Les cellules adjacentes sont en forme de croix autour de la cellule étudiée ![](https://i.imgur.com/YG85F69.png) <div align="center">Fig.9 Schéma des cellules 1</div> * L'ordre de vérification est ainsi ![](https://i.imgur.com/6KnKXk9.png) <div align="center">Fig.10 Schéma des cellules 2</div> * Si les cellules sont éligibles à l'ajout, alors on sauvegarde leurs ids dans une liste d'entiers. * Après avoir récupéré les ids des cellules utiles, on parcourt cette liste et on vérifie les critères d'ajout. * Si la cellule n'est pas dans la liste fermée, on calcule ses coûts et on l'ajoute à la liste ouverte * Une fois les coûts calculés, on regarde si la cellule est présente dans la liste ouverte. Si oui, on compare avec son homologue : si les nouveaux coûts sont meilleurs, on la remplace, sinon on ne fait rien. * Si elle n'est pas présente dans la liste ouverte, on l'ajoute. ###### 5.4.2.2.4 Création du chemin Lorsque la destination est atteinte, il faut reconstruire le chemin. Comme chaque cellule possède un lien à son parent, on part de la fin de la liste fermée et on remonte le courant jusqu'à arriver à la case de départ. Chaque étape est ajoutée à une liste de cellules afin d'avoir un chemin final à rendre. ```cpp= // Create the path from the end to the beginning void BattleWindow::traceWay(listNode & closedList, QList<Cell*> & path, Cell * destination) { // Create a temporary node which is used to represent the destination node tmp = closedList[destination->getId()]; // Copy the destination cell Cell * cell = destination; // Get the parent id from the tmp node int previous = tmp.parentID; // Add the cell to the path path.push_front(cell); // As long as it's not the end of the path while(previous != -1){ // Get the previous cell cell = map->getListCells()[previous]; // Add it to the path path.push_front(cell); // Set the next id to be added tmp = closedList[tmp.parentID]; previous = tmp.parentID; } } ``` ###### 5.4.2.2.5 Pathfinding L'étape finale de l'implémentation est la fonction qui va appeler toutes les autres pour trouver le meilleur chemin entre deux cellules. On commence par créer les listes ouvertes et fermées, ensuite, on récupère les différentes cellules importantes (Départ et arrivée). Ensuite, on ajoute la cellule de départ à la liste ouverte et à la liste fermée puis on inspecte les premières cellules adjacentes. Tant que la destination n'est pas atteinte : * On récupère la meilleure cellule de la liste ouverte en tant que cellule étudiée * On l'ajoute à la liste fermée * On ajoute ses cellules adjacentes à la liste ouverte ```cpp= // Final method for the pathfinding QList<Cell*> BattleWindow::pathfinding(Cell* dest, Cell* currentCell) { // list used to study the path listNode closedList; listNode openList; // list final QList <Cell *> path; // Get the destination Cell * destination = dest; // Get the ide of the currentCell (departure) int current = currentCell->getId(); // node created which represents the departure node departure; // value which means it's the first cell (special value to set the beginning of the path) departure.parentID = -1; // add the start to the openList openList[current] = departure; // then add it to the closedList (the start is mandatorily in the path) addClosedList(closedList, openList, current); // start to add nearby cells to the openList addNearbyCell(current, closedList, openList, destination); while((current != destination->getId()) && (!openList.empty())){ // get the best node in the openList current = bestNode(openList); // add the best node to the closedList (best path possible) addClosedList(closedList, openList, current); // we start the search all over again until we get to the destination addNearbyCell(current, closedList, openList, destination); } // if the destination is reached, we get back up to the beginning to recreate the path if(current == destination->getId()){ // get the path back traceWay(closedList, path, destination); } return path; } ``` #### 5.4.3 Gestion de tour et d'état Ici, le sujet traité est le comportement de la Battle Window. Comment la gestion des tours est faite, les différents modes entre celui de déplacement et de combat, la phase de spawn et la gestion des différents clics sur la fenêtre. ##### 5.4.3.1 Passer le tour Lors de la phase de déplacement, le texte du bouton affiche "Placement ennemi". En effet, lorsque le joueur aura fini de placer ses unités, il devra placer les unités ennemies où bon lui semble. Une fois fait, il devra appuyer sur le bouton qui affichera cette fois "Commencer" et débutera le jeu par un tour de l'IA. À ce moment-là, le bouton affiche "Fin du tour". Ce bouton n'est pas cliquable en phase de déplacement. En phase de combat, quand le joueur clique sur le bouton le tour va passer à l'IA, qui jouera automatiquement son tour, une fois son tour terminé, c'est à nouveau à l'utilisateur de jouer. Lorsque le tour passe, les mouvements et droits d'attaque des unités se réinitialisent. ##### 5.4.3.2 Passer en mode combat Ce bouton n'est cliquable que quand nous sommes en phase de déplacement. Lorsqu'on clique sur ce bouton, on passe en phase d'attaque et toutes les unités peuvent attaquer une fois. ##### 5.4.3.3 Clic sur une unité Lorsque le slot reçoit le signal et se lance, il va afficher les stats de l'unité sélectionnée et mettre son contour en jaune. Le slot va vérifier si nous sommes en phase de spawn. Si c'est le cas alors on va juste récupérer toutes les cellules de spawn qui sont correspondantes à l'équipe à laquelle elles appartiennent. Si nous sommes en phase de mouvement, il appelle la méthode de portée avec comme paramètre la portée de déplacements qui retourne toutes les cases accessibles. Le slot colore ensuite en vert les cases pour donner un feedback visuel à l'utilisateur. Si la phase actuelle est celle de combat et si aucune unité n'est sélectionnée alors on va sélectionner l'unité, récupérer sa distance d'attaque avec la méthode de portée et la colorier en rouge pour donner un feedback visuel à l'utilisateur. Si une unité est déjà sélectionnée, ça veut dire que l'unité cliquée va être attaquée. On va donc d'abord vérifier que l'unité n'est pas alliée, si ce n'est pas le cas alors on va vérifier qu'elle est à portée d'attaque. Si c'est le cas alors on va l'attaquer avec l'unité sélectionnée et si elle n'a plus de vie alors on la retire de la liste à laquelle elle appartient et on la retire de la scène. Si la liste n'a plus d'unité alors on active le signal pour la victoire. ##### 5.4.3.4 Clic sur une cellule en phase de mouvement Si la case sur laquelle on clique fait partie des cases accessibles par l'unité sélectionnée, alors on lance une fonction qui va vérifier si nous sommes en phase de spawn, si c'est le cas alors on bouge l'unité à la case cliquée et ne retirons pas de points de mouvement. Si nous cliquons sur une case avec une autre unité à l'intérieur alors les unités changent de positions. Si c'est la phase de mouvement, alors on va récupérer le chemin pour aller à cette cellule via le pathfinding et retirer à l'unité le nombre de points de mouvement correspondant au nombre de cases du chemin et on bouge l'unité à sa nouvelle position. ### 5.5 IA L'IA a un fonctionnement très simple. En premier lieu, elle va trier ses unités selon leurs distances avec l'ennemi. Plus une unité est proche, plus elle sera prioritaire dans la liste. En second lieu, elle va récupérer sa cible : l'ennemi le plus proche de l'unité sélectionnée. En troisième, elle va se déplacer au plus proche de sa cible et finalement, si elle a la portée, elle va attaquer sa cible. #### 5.5.1 Tour de l'IA ##### 5.5.1.1 Choix des unités Comme dit précédemment, l'IA va trier ses unités selon une distance. C'est pourquoi deux méthodes essentielles sont d'abord implémentées. La première étant un calcul de distance en cases par Pythagore entre deux unités. La seconde étant une fonction qui récupère la distance la plus petite entre une unité IA et la liste des unités du joueur. ###### 5.5.1.1.1 Tris Finalement, c'est en utilisant les deux fonctions ci-dessus que l'on va trier la liste des unités avec un tri rapide (Quick sort). ##### 5.5.1.2 Choix de la cible Maintenant que la liste des unités est triée, il faut sélectionner la cible pour chaque unité. La cible d'une unité est définie comme étant l'unité ennemie la plus proche. ##### 5.5.1.3 Destination de l'unité À chaque tour, l'unité va récupérer une destination avant de se déplacer. Cette destination est, en général, la cellule présente dans sa zone de déplace et qui est la plus proche de sa cible. Une fois acquise, elle s'y déplace. ##### 5.5.1.4 Fonction finale et attaque de la cible Voici la fonction finale, c'est dans celle-ci que l'IA va jouer chaque tour : 1. Tri de ses unités. 2. Sélection de cible et déplacement pour chacune de ses unités. 3. Passage en mode combat. 4. Si l'unité le peut, elle attaque sa cible, sinon elle passe son tour. 5. Si lorsqu'elle tue une unité, il n'en reste plus, alors elle a gagné. 6. À la fin de chaque tour, elle réinitialise ses stats de déplacement. ```cpp= // Function used to make the AI play void BattleWindow::IATurn(){ // Get the AI (Player) as a AI AI * ia = static_cast<AI*>(this->ai); // Sort the order of the team ia->teamSort(this->human, 0, this->ai->getArmyList().size()); for(Unit * unit : ia->getArmyList()){ this->unitSelected = unit; // Set a target to the current unit ia->target(unitSelected, this->human->getArmyList()); // Move the unit to its destination if(unitSelected->getTarget() != nullptr){ Cell * destination = destinationTarget(unitSelected, unitSelected->getTarget()); if(destination != unit->getCell()){ this->moveUnitToCell(destination); } } } // Combat mode state = ATTACK; for(Unit * unit : ia->getArmyList()){ this->unitSelected = unit; ia->target(unitSelected, this->human->getArmyList()); Unit * target = unitSelected->getTarget(); // The current unit attack its target if it's in the range QList<Cell *> attackRange = cellsInRange(unitSelected->getRange(), unitSelected->getCell()); if(attackRange.contains(target->getCell())){ // Then the unit can attack its target unitSelected->Attack(target); // Check if the unit is dead if(target->getHP() <= 0){ // if it is, remove it from the map and the list map->removeUnit(target); if(human->getArmyList().contains(target)){ human->removeUnit(target); // finally, check if the AI has won the game if(human->getArmyList().size() == 0){ //Win IA emit winner(false); break; } } } } } // Pass the turn state = MOVEMENT; // reset the movements of ia's units if(!(ia->getArmyList().empty())){ for(Unit* u : ia->getArmyList()){ u->resetMove(); } } clearCells(); } ``` ### 5.6 Interface graphique Les interfaces graphiques sont très importantes. C'est avec celles-ci que l'utilisateur va interagir. Elles doivent être facile d'utilisation et instinctives. Le design est aussi un aspect prioritaire pour que le joueur puisse avoir une interface agréable à regarder. Ici, le sujet de ce chapitre est de montrer les différentes interfaces graphiques que composent l'application ainsi que de comparer les changements faits lors de la réalisation avec les schémas proposés à l'analyse. #### 5.6.1 Menu principal Le menu principal conserve la même composition que durant notre analyse. Il contient trois boutons : nouvelle partie, règles du jeu et un bouton pour quitter l'application. Ce qui change avec notre analyse est la couleur des boutons. ![](https://i.imgur.com/94jOIRb.png) <div align="center">Fig.11 Interface du menu principale</div> #### 5.6.2 Army Selection Cette interface permet à l'utilisateur de composer son armée et celle de l'IA si l'option a été choisie. On y retrouve à gauche et à droite, les deux listes des armées, alliée et ennemie. En bas le nombre de pièces d'or qu'ils leur restent. Ensuite au centre se trouve les trois unités qui sont possibles d'acheter. Quand le joueur clique sur une unité, sa description se trouve en dessous des unités au centre de la fenêtre. Dans cette description, il y a toutes les caractéristiques de l'unité. Pour finir, en dessous de la description se trouvent trois boutons. Le premier permet l'ajout de l'unité sélectionnée dans la liste active, si l'armée a assez de pièces d'or. Le deuxième bouton s'active si l'utilisateur clique sur une unité dans la liste. Au moment du clic sur le bouton, l'unité va être supprimée et les pièces d'or vont être remboursées. Les deux listes à gauche et à droite sont des widgets ArmyList. Ce widget contient le titre de l'armée, le texte qui permet de savoir si c'est son tour, le QListWidget qui est la liste des unités et les pièces d'or. ![](https://i.imgur.com/fRNtysw.png) <div align="center">Fig.12 Interface de sélection des armées</div> Par rapport au schéma de l'analyse, les sprites des unités ont changé. Créer nos propres personnages a été un choix pris au cours du développement. Il y a aussi l'image de fond qui a été retirée, car celle-ci faisait de trop sur l'interface. Il y a l'ajout du texte en dessous des armées pour préciser à qui est le tour. Les autres changements sont des modifications mineures de design. #### 5.6.3 Battle Window Cette interface est celle de la phase de jeu. C'est là où les deux armées vont combattre. La battle window est devenue plus complète. Il y a la map au centre où les personnages vont se déplacer dessus. Sur le côté droit se trouve le widget DisplayStats qui permet d'afficher les caractéristiques actuelles de l'unité qui est sélectionnée. Il y a aussi son sprite qui est affiché en grand. Du côté droit, c'est le widget ButtonAction qui affiche deux boutons et un texte au centre pour dire au joueur ce qu'il peut faire. Le choix de ces modifications a été fait, car il y avait un manque de compréhension. La première idée de cette interface était aussi beaucoup trop simpliste. ![](https://i.imgur.com/9VGi8bc.png) <div align="center">Fig.13 Interface du plateau de jeu</div> #### 5.6.4 Menu de fin Le menu de fin est très simpliste. Le design a plus été concentré sur les interfaces de sélection des armées et du plateau de jeu qui sont plus importantes. Dans ce menu de fin, au centre, il y a le nom du gagnant de la partie et en bas de l'écran se trouve les boutons pour rejouer une nouvelle partie, un autre pour revenir sur la fenêtre de création des armées et le dernier bouton est pour quitter le jeu. ![](https://i.imgur.com/vIjroAd.png) <div align="center">Fig.14 Interface du menu de fin</div> ## 6. Tests ### 6.1 Test par une personne externe Le cobaye pour ce test a été le frère de monsieur Costa, informaticien lui aussi et qui a déjà eu son bachelor. Le programme a été testé plusieurs fois dans des cas différents : 1. Lancement d'une partie contre l'IA en mode difficile où le joueur a créé une armée très conséquente. Il a testé tous les types de mouvements des unités, est-ce que l'on peut attaquer son allié ou non (on ne peut pas), il voulait voir ce qu'il se passe lorsqu'il gagnait, mais il a perdu. Il a alors essayé de rejouer, mais cette fois-ci en sélectionnant une armée ennemie plus légère. 2. Lors de la deuxième partie face à une armée créée par lui-même, il a essayé de produire des bugs et c'est là qu'il a trouvé un bug d'affichage lorsque l'on cherche à voir les stats d'une unité qui a terminé de se déplacer. Un second bug est apparu où une unité n'agissait pas toujours comme un mur pour une unité souhaitant se déplacer ou attaquer. Il a fini la partie en gagnant face à l'IA sans trop détonner d'autres informations. Ces remarques et notifications en sont ressorties : * Il y avait quelques problèmes de compréhension, notamment au niveau de quelles actions devaient être réalisées à quel moment. * Pour résoudre ces problèmes, nous avons modifié les textes, au lieu d'indiquer à qui est le tour, nous indiquons les actions qui doivent être réalisées au moment ou l'utilisateur lit le texte. Les boutons ont aussi des textes différents pour qu'ils soient plus compréhensibles. * Lors de la sélection d'armées, pouvoir cliquer dans le vide pour désélectionner les unités * Qu'une unité soit automatiquement sélectionnée comme potentiel ajout lors de la sélection d'armées * Un système de log ou un son qui indique au joueur quelles ont été les répercussions de son action. * Il n'arrivait pas à bien différencier les unités qui avaient déjà fait une action ou non. Par exemple, il ne savait pas quelles unités il avait déjà déplacées. * Nous avons implémenté un système de "wasted", si l'unité s'est déplacée de tous ses points de mouvements disponibles, alors elle se grise, pareil lors d'une attaque. Comme ça l'utilisateur a un feedback visuel de si l'unité a attaqué ou de si elle a encore des points de mouvement disponibles. * Le fait de devoir d'abord déplacer toutes ses unités puis attaquer n'est pas très intuitif. ### 6.2 Bugs * Quelques bugs subsistent : * Une unité ne sert pas tout le temps de mur. * La portée d'attaque d'IA est quelques fois faussée lorsqu'elle est au contact d'une unité. En effet, elle perçoit quelques fois l'unité comme un mur, ce qui force l'unité à se redéplacer pour mieux réattaquer. * Ces deux problèmes sont liés, la raison est que quand l'algorithme de portée voit une unité, c'est comme s'il la voyait, mais juste l'ignore et continue son chemin. C'est lié au parcours de l'algorithme et pour le résoudre il faudrait casser le parcours de l'algorithme, mais le problème qui survient c'est que certaines cellules ne seront pas analysées. Pour résoudre ce problème, l'idée serait encore d'ajouter une boucle, mais c'est plus un problème de conception trop grand. Dans la globalité le jeu est bien malgré les quelques soucis de jouabilités et bugs qui peuvent impacter le gameplay. ## 7. Résultats Dans ce projet le cahier des charges a été pratiquement respecté, cependant certains points ne l'ont pas étés. Notamment dans la sélection d'armées, à la base c'était indiqué qu'un mode aléatoire serait disponible, mais il a été retiré, car estimé que ça n'apportait rien dans le projet. Il y a aussi les effets des cellules sur les unités, par exemple si c'est une forêt l'unité doit être ralentie si ce n'est pas un archer. Nous avons décidé de mettre cette tâche dans les priorités basses et ne sommes pas allés suffisamment loin dans nos priorités. Le fait que des unités aient des pouvoirs a également été mis dans les priorités basses. Dans les spécifications d'autres points peuvent être utilisés pour améliorer le projet. Le fait que le joueur ne puisse pas bouger les unités ennemies s'il coche la sélection par IA était l'idée de base. Faire en sorte que le joueur puisse toujours bouger les unités ennemies est un choix fait durant l'implémentation, mais l'idée de base n'est pas totalement écartée. Les objets sont une bonne idée pour approfondir le gameplay, mais ils n'étaient pas essentiels et sont passés dans les priorités basses. La caméra a également été retirée, elle peut être utile notamment pour mieux voir ce qu'il se passe, mais jugée peu maniable et donc désagréable à l'utilisation. Plusieurs autres améliorations peuvent être faites, il y a quelques bugs qui subsistent dans l'algorithme de portée, parfois certaines unités ne sont pas prises en compte comme "mur" et l'unité sélectionnée peut passer à travers. L'algorithme de pathfinding a lui aussi encore quelques imperfections, les unités se déplacent alors qu'elles n'en ont pas besoin et pour résoudre ce souci c'est lié au fait qu'une unité est parfois considérée comme un mur. Finalement, parfois les unités restent bloquées à certains endroits ne sachant pas trop comment avancer, la raison est comme elles utilisent leur portée de mouvement pour se déplacer, elles vont juger leur position actuelle comme meilleure par rapport aux options où elles devraient reculer. ## 8. Conclusion En conclusion de ce projet P2, grâce à une bonne cohésion de groupe, nous sommes arrivés à un projet qui respecte en majeur parti le cahier des charges. En effet, le jeu "Dwar" est stable et présente uniquement des bugs mineurs cités dans cette documentation. Comme indiqué dans le paragraphe [résultat](##7.-Résultats), bien qu'il puisse encore être amélioré, l'état actuel du projet satisfait les objectifs premièrement créés. ### 8.1 Résumé des objectifs du module 1. Apprentissage de l'utilisation de Qt - Pour l'ensemble du groupe, bien qu'impossible à maîtriser complètement, Qt a été bien appris. 2. Création d'une application graphique via Qt - Le jeu Dwar est une application graphique sobre, simple et fonctionnelle. L'objectif est atteint. 4. Gérer un projet à plusieurs (planning, suivi, analyse ...) - Malgré un problème de retard, le planning et les différentes tâches de la gestion du projet ont été gérées au mieux possible. 5. Rendre un programme stable - L'application est stable et n'a pas de bug majeur qui ferait planter le programme. ### 8.2 Résumés des objectifs du projet 1. Créer des interfaces graphiques intuitives pour le jeu Dwar - Dwar possède plusieurs interfaces graphiques qui correspondent aux différents menus du jeu. L'objectif est donc atteint. 2. Le jeu utilise trois unités différentes - Les unités du jeu sont les Saber, Archer ou Rider (Épéiste, archer ou cavalier) 3. L'adversaire est une IA - L'adversaire est une IA qui a différentes armées selon son mode de difficulté et maîtrise ses unités face à nous. 4. Un plateau de jeu sous forme d'échiquier où l'on peut faire se battre les unités alliées face aux unités ennemies. - Un plateau est fait et l'utilisateur peut poser, déplacer et attaquer des unités. 5. L'utilisateur peut acheter des objets et les utiliser durant la partie - L'objectif des objets n'a pas été atteint. 6. Les unités possèdent des pouvoirs différents par classe - L'objectif des pouvoirs n'a pas été atteint. 7. Un menu de fin permettant de rejouer et de retourner à la configuration - L'objectif est rempli, un menu de fin est présent lorsque l'une des deux équipes gagne. ## 9. Annexes #### Diagramme UML sur lucidchart * (Diagramme_UML_P2.pdf) dans le dossier ressources * (Diagramme_UML_P2_Simplifie.pdf) dans le dossier ressources #### GANTT * (gantt.pdf) dans le dossier ressources #### Journal de travail * Tous les journaux de travail sont dans le [Wiki](https://gitlab-etu.ing.he-arc.ch/isc/2021/p2qt/Dwar/-/wikis/home) du gitlab #### Cahier des charges * [Cahier des charges](https://gitlab-etu.ing.he-arc.ch/isc/2021/p2qt/Dwar/-/wikis/home) ## 10. Bibliographie #### [1] FREE RPG Tileset 32x32, Pipoya, pipoya.itch.io/pipoya-rpg-tileset-32x32 #### [2] Comment lire un fichier XML avec Qt ?, Le blog de Lulu, 26.10.2019, lucidar.me/fr/dev-c-cpp/reading-xml-files-with-qt/ #### [3] Dwarf Pixel Art, ObinSun, Pinterest, pinterest.ch/pin/438678819937103064/ #### [4] Recherche de chemin par l'algorithme A*, khayyam90, 23.08.2006, Developpez, khayyam.developpez.com/articles/algo/astar/# #### [5] The new Qt5 connection syntax, RIP Tutorial, riptutorial.com/qt/example/7932/the-new-qt5-connection-syntax #### [6] Deployment and installation of a Qt application in Microsoft Windows, Wallefox, walletfox.com/course/qtinstallerframeworkexample.php #### [7] Tutorial: Creating an Installer, Qt, doc.qt.io/qtinstallerframework/ifw-tutorial.html #### [8] Coin, Kalash, iconfinder.com/icons/2907505/cent_coin_currency_dollar_usd_valuta_icon #### [9] Tiled, mapeditor.org/ #### [10] Pathfinding: A* Search Algorithm, Karl Matthes, 06.06.2020 karlmatthes.medium.com/pathfinding-a-search-algorithm-d77400c89888

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully