Skip to content

Scaldev/TowerDefense

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TowerDefense


Fonctionnement du jeu

Objectif

Tower Defense consiste à défendre une base de vagues d'ennemis à l'aide de tours.

Plateau de jeu. Chaque niveau se déroule sur un plateau comportant une base verte que le joueur doit défendre, ainsi qu'une base rouge sur laquelle les ennemis apparaissent.

Ennemis. Les ennemis avancent de la base rouge à celle du joueur en prenant l'unique chemin existant de l'un à l'autre. Si un ennemi arrive à la base du joueur, il attaque une fois les points de vie du joueur avant de disparaître.

Tours. Le joueur possède une certaine somme d'argent qu'il peut dépenser dans la boutique pour acheter et placer des tours. Celles-ci attaquent les ennemis. Un ennemi tué par les tours rapporte une certaine somme d'argent au joueur.

Victoire. Le joueur gagne un niveau s'il survit jusqu'à ce qu'il n'y ait plus d'ennemi en vie et que toutes les vagues d'ennemis sont passées. Il perd si son nombre de points de vie atteint zéro.

ContrĂ´les

Dans le menu :

  • ENTER : Lance le jeu avec la sauvegarde affichĂ©e Ă  l'entrĂ©e, ou avec une nouvelle sauvegarde si New game est affichĂ©.

  • <- et -> : Permet de parcourir les sauvegardes disponibles.

  • D : supprime la sauvegarde affichĂ©e, s'il y en a une.

Dans un niveau :

  • ECHAP : Retour au menu.

  • LEFT CLICK : Permet de sĂ©lectionner une tour dans la boutique, et de placer la tour sĂ©lectionnĂ©e sur une case de construction.

  • A, B, C, D : Si un marchand est Ă  la base du joueur, sĂ©lectionne l'offre correspondante Ă  la lettre pressĂ©e.

On a également les touches de triche suivantes :

  • W : Ajoute 1 coin au joueur.

  • N : Passe au niveau suivant.


Fonctionnalités réalisées

Dans cette section, on décrit l'implémentation des fonctionnalités générales qu'il fallait réaliser.

Plateau de jeu

Dans tout le projet, on utilise la classe $\texttt{Coords}$ qui représente une paire d'entiers.

On distingue cinq types de cases, qui étendent $\texttt{Tile}$ :

  • $\texttt{BaseTile}$ : la base du joueur, en vert.
  • $\texttt{SpawnTile}$ : lĂ  oĂą les ennemis apparaissent, en rouge.
  • $\texttt{RouteTile}$ : lĂ  oĂą les ennemis se dĂ©placent, en bleu clair.
  • $\texttt{ConstructionTile}$ : lĂ  oĂą des tours peuvent ĂŞtre placĂ©es, en bleu.
  • $\texttt{WallTile}$ : une case de remplissage, en bleu foncĂ©.

Le plateau de jeu est implémenté comme une matrice de cases $\texttt{Tile[][]}$.

Le chemin est implémenté comme une pile de coordonnées $\texttt{Stack}$. Chaque coordonnées de la pile correspond au milieu d'une case du plateau.

Entités

Les ennemis et les tours dérivent d'une classe abstraite d'entité, notée

Entity<A extends Entity<A, B>, B extends Entity<B, A>>

On justifie ce choix.

D'une part, on souhaite pouvoir appeler sur $\texttt{Entity}$ une méthode abstraite $\texttt{attack}$ qui fournit les dégâts que l'entité souhaite infliger à chaque ennemi. Le type des entités attaquées dépend cependant de celui de l'entité elle-même, on doit donc paramétrer la classe $\texttt{Entity}$ d'un type $\texttt{B}$ représentant celui des ennemis d'une entité.

D'autre part, comme on le verra plus tard, on souhaite pouvoir appeler des manipuler les status de l'entité (ajouter, retirer, obtenir). Cependant, les status sont eux-même paramétrer du type de l'entité. En effet, un statut est soit pour les ennemis, soit pour les tours. Similairement, on paramétrise $\texttt{Entity}$ d'un type $\texttt{A}$ qui indique le type d'entité ciblé par les status.

En particulier, pour une sous-classe de $\texttt{Entity}$, la classe $\texttt{A}$ est précisément cette sous-classe. En corrolaire, on en déduit le nom de nos classes-filles d'$\texttt{Entity}$ :

  • $\texttt{Enemy extends Entity&lt;Enemy, Tower&gt;}$, la classe des ennemis.
  • $\texttt{Tower extends Entity&lt;Tower, Enemy&gt;}$, la classe des tours.

Ennemis

La classe des ennemis est $\texttt{Enemy}$, qui étend la classe $\texttt{Entity}$. Tous les ennemis ont été implémentés.

Déplacement. Les ennemis se déplacent via une méthode $\texttt{move}$. Ils suivent pour cela le chemin vers le base qu'on leur fournit via la méthode $\texttt{initPath}$, représenté par une pile de coordonnées $\texttt{Stack}$. Les ennemis vont dans la direction des coordonnées en tête de la pile, et une fois celles-ci atteintes, la tête de la pile est retirée et on recommence, et ce jusqu'à ce que la pile soit vide, auquel cas l'ennemi a atteint la base.

Attaque. Chaque ennemi défini sa manière d'attaquer les tours. Pour cela, à partir d'une liste de tours donnée, il renvoie une liste d'attaques, une par tour donnée en entrée.

Équilibrage. Les valeurs données dans l'énonce mènent à un jeu plutôt déséquilibré et injouable. Pour pallier à cela, nous avons modifié ces valeurs pour gagner en équilibrage, et donc en fun.

Tours

La classe des tours est $\texttt{Tower}$, qui étend la classe $\texttt{Entity}$. Toutes les tours ont été implémentées.

Boutique. Une tour s'achète dans la boutique, implémentée par la classe $\texttt{Shop}$. Un attribut majeur de cette classe est son tableau $\texttt{Slot[]}$ de slots. Chaque slot représente un produit de la boutique, qui est une tour. Cliquer sur la slot d'une tour la sélectionne, et on peut lire ses détails dans la case dédié à la slot sélectionnée.

Placement. Si une tour est sélectionnée, que le joueur a assez d'argent pour l'acheter et qu'il clique sur une $\texttt{ConstructionTile}$ qui n'est pas occupée, alors la tour est placée et le joueur est débité du montant indiqué.

Attaque. Chaque tour définie sa manière d'attaquer les ennemis, de manière symétrique à comment les ennemis attaquent les tours. Cela va permettre la création de méthodes polymorphiques par la suite.

Gestionnaire de combat

Nous avons décrit les deux bélligérants principaux de notre jeu, nous pouvons donc décrire la manière principale par laquelle ils interagissent. Les interactions se font dans une classe $\texttt{FightManager}$. Celle-ci contient seulement deux attributs :

  • La liste des tours actuellement vivantes et affichĂ©es ;
  • La liste des ennemis actuellements vivants et affichĂ©s.

Ă€ chaque frame, le gestionnaire de combat :

  • Met Ă  jour les ennemis, les tours, ainsi que le marchĂ© et l'inventaire du joueur (voir plus loin).
  • Fait attaquer les ennemis et les tours ;

Le polymorphisme développé à travers les classes précédentes permet d'utiliser d'uniques fonctions polymorphiques capables de traiter à la fois l'attaque des ennemis envers les tours, et des tours envers les ennemis.

Jeu

Jeu. Le jeu est défini dans la classe $\texttt{Game}$. Il ne peut exécuter qu'un niveau à la fois, ce que représente l'attribut $\texttt{level}$ de cette classe. Au lancement, le jeu charge le nom de tous les fichiers de niveaux que contiennent le jeu. Le contenu d'un niveau n'est chargé que lorsque celui-ci commence.

Joueur. La classe $\texttt{Joueur}$ contient les informations spécifiques au joueur, c'est-à-dire ses points de vie, sa quantité d'argents et son inventaire.

Niveau. Un niveau est composé d'un joueur de la classe $\texttt{Player}$, d'une boutique de la classe $\texttt{Shop}$, d'un gestionnaire de combat $\texttt{FightManager}$, d'une liste de vagues d'ennemis $\texttt{Queue}$. Un niveau commence en générant une première vague d'ennemis, et passe à la suivante quand tous les ennemis sont morts (ce que détermine le gestionnaire de combat). Si le joueur meurt, ou si la liste de vagues est vide, alors le niveau est terminé.

Écran. L'écran d'un niveau est composé de plusieurs zones, dont la classe $\texttt{Zone}$ permet une représentation.

  • La classe/zone $\texttt{Level}$ affiche les informations propres au niveau.
  • La classe/zone $\texttt{Player}$ affiche les informations propres au joueur.
  • La classe/zone $\texttt{Shop}$ affiche les tours disponibles dans la boutique, ainsi que la tour sĂ©lectionnĂ©e.
  • La classe/zone $\texttt{Gameboard}$ affiche le plateau de jeu et tout ce qui se passe dessus (notamment les entitĂ©s).

Fonctionnalités bonus et supplémentaires

Dans cette section, on traite en même temps les fonctionnalités bonus proposées dans le sujet, ainsi que les fonctionnalités supplémentaires. Cela s'explique par le fait que certaines fonctionnalités bonus, pour être implémentées proprement, ont demandé l'implémentation d'un système plus général et robuste. C'est notamment le cas du marchand.

Menu et sauvegarde

Menu. Une fonctionnalité majeure que nous avons ajouté est celle d'un menu permettant d'harmoniser le passage entre les niveaux. Ainsi, au lancement du jeu, un menu de sélection de sauvegarde est affiché. Il permet de sélectionner une sauvegarde parmi celles trouvées dans le dossier resources/saved, et de la lancer en appuyant sur la touche Entrée. Il permet également de créer une nouvelle sauvegarde en sélectionnant New game.

Sauvegarde. Chaque nouveau niveau atteint met à jour la sauvegarde. De même, nous enregistrons la vague maximale atteinte pour le dernier niveau atteint. Cependant, relancer une sauvegarde à une vague en milieu de niveau, sans sauvegarder ni l'argent du joueur, ni les tours posées, peut mener à un softlock, si la vague qui arrive requiert d'avoir posé des tours aux vagues précédentes. Pour pallier à ce problème, nous avons décidé que le lancement d'une sauvegarde fasse commencer le joueur au début du niveau, c'est-à-dire à la première vague. Il serait facilement possible de le faire commencer à la vague enregistrée en passant en argument du constructeur $\texttt{Level}$ le numéro de la vague enregistrée, et de directement passer à cette vague.

Écran de transition

Transition. La transition entre les niveaux peut paraître abrupte, puisque dès que le joueur gagne, le niveau suivant est lancé. Pour améliorer l'expérience utilisateur, nous avons créé une classe $\texttt{TransitionScreen}$ qui fait une fondue noir, attend quelques temps et redevient transparent.

Début et fin de niveau. De cette classe, deux classes filles existent : $\texttt{LevelScreen}$, pour le début de niveau, et $\texttt{ResultsScreen}$, pour la fin de niveau. Le premier affiche le nom du niveau, et le second le résultat (victoire ou défaite). Le rendu est clairement visible à l'expérience.

Statuts

Une entité peut posséder des statuts qui alternent ses valeurs, comme son attaque ou ses points de vie. Un statut est une instance d'une classe implémantant la classe abstraite $\texttt{Status&lt;E extends Entity&gt;}$. Il est donc paramétré soit comme étant pour les tours, soit pour les ennemis. De plus, un statut peut être positif comme négatif. Par exemple, Regeneration fait regagner des points de vie aux tours, tandis que Frozen diminue la vitesse et la vitesse d'attaque des ennemis.

Ce système s'avère très utile dans l'implémentation de certaines tours (Ice Caster et Poisoned Caster), ainsi que le marchand.

Message et boost

Boost. Un ennemi qui implémente l'interface $\texttt{Helper}$ peut également soigner des ennemis avec sa méthode $\texttt{help}$. En particulier, il peut soigner les points de vie des autres ennemis, mais aussi leur donner des statuts (supposément positifs).

Message. Les entités peuvent afficher un message à l'écran, sous forme de bulle de dialogue, pour une certaine durée. C'est notamment le cas des ennemis Minion, Termiernator et MerchantKing.

Marché et marchand

Le marché est une classe $\texttt{Market}$ faisant le pont entre les MerchantKing et le Player. Il est ainsi ce que le FightManager est aux ennemis et aux tours. Lorsqu'un marchand atteint la base, il rejoint le marché et propose des offres au joueur, qu'il explicite par un message. Le joueur peut alors appuyer sur les touches A, B, C et D pour sélectionner une offre parmi les quatres proposées par le marchand. Une fois l'offre sélectionnée, le marchand part.

Au lieu de propose les trois même offres systématiquement, nous avons amélioré le marchand pour qu'il propose trois de quatre offres aléatoirement parmi six offres. La quatrième offre, qui est de gagner 30 pièces, est systématiquement présente, afin qu'un joueur ruiné puisse tout le même bénéficier de l'aide du marchand.

Cependant, par notre implémentation, les bonus du marchand ne sont pas conservés entre les niveaux. Cela permet que les niveaux soient indépendants entre eux, et afin d'éviter les comportements hasardeux avec le système de sauvegarde.

Exception

Les exceptions sont toutes implémentées. Nous avons légèrement modifié l'arborescence entre les différentes exceptions, en définissant l'exception $\texttt{MapException}$ comme mère de toutes les exceptions relatives au chemin du plateau de jeu. Ainsi, l'exception produite en cas de mauvais format de plateau de jeu s'appelle, en toute logique, $\texttt{MapFormatException}$.

About

🏰 A Tower Defense game in Java.

Resources

Stars

Watchers

Forks

Contributors 2

  •  
  •  

Languages