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.
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.
Dans cette section, on décrit l'implémentation des fonctionnalités générales qu'il fallait réaliser.
Dans tout le projet, on utilise la classe
On distingue cinq types de cases, qui étendent
-
$\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
Le chemin est implémenté comme une pile de coordonnées
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
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
En particulier, pour une sous-classe de
-
$\texttt{Enemy extends Entity<Enemy, Tower>}$ , la classe des ennemis. -
$\texttt{Tower extends Entity<Tower, Enemy>}$ , la classe des tours.
La classe des ennemis est
Déplacement.
Les ennemis se déplacent via une méthode
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.
La classe des tours est
Boutique.
Une tour s'achète dans la boutique, implémentée par la classe
Placement.
Si une tour est sélectionnée, que le joueur a assez d'argent pour l'acheter et qu'il
clique sur une
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.
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
- 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.
Le jeu est défini dans la classe
Joueur.
La classe
Niveau.
Un niveau est composé d'un joueur de la classe
Écran.
L'écran d'un niveau est composé de plusieurs zones, dont la classe
- 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).
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.
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
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
Début et fin de niveau.
De cette classe, deux classes filles existent :
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
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.
Boost.
Un ennemi qui implémente l'interface
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.
Le marché est une classe 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.
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