Réalisation d’un jeu vidéo 2D : UBGarden.
Une princesse est perdue et désespérée. Votre mission, si vous l’acceptez, est d’aller la secourir. Pour cela, vous devrez traverser plusieurs jardins, plus fantastiques les uns que les autres.
Chaque jardin est représenté par une carte (rectangulaire) composée de cellules.
Des portes vous permettront de passer de jardin en jardin.
Certaines portes seront fermées à clé et nécessiteront d’avoir une clé pour les ouvrir.
Attention, des abeilles vivent dans ces jardins et vous êtes allergique aux piqures.
Le joueur et les abeilles peuvent se déplacer de cellule en cellule, dans un espace géométrique discret.
Les arbres sont des éléments de décor infranchissables.
Seules les abeilles peuvent traverser les bosquets de fleurs, le joueur ne peut pas s’y aventurer.
Chaque déplacement du joueur consomme de l’énergie, en fonction du type de sol sur lequel il marche.
Ce coût de déplacement est multiplié par diseaseLevel
, initialisé à 1.
Les abeilles peuvent voler autant qu’elles veulent sans jamais se fatiguer.
Le joueur récupère vite et gagne un point d’énergie au bout de energyRecoverDuration
secondes sans bouger, dans la limite de son niveau d’énergie initial playerEnergy
.
Le niveau d’énergie actuel du joueur est affiché dans la barre d’état.
En cas de piqure d’une abeille, vous perdez un point de vie, mais l’excès d’adrénaline vous rendra invincible pendant playerInvincibilityDuration
secondes.
Les abeilles ne survivent pas aux piqures qu’elles vous infligent.
Heureusement, vous avez plusieurs points de vies et pouvez ramasser des cœurs pour augmenter vos points de vies.
Le nombre de vies restantes du joueur est affiché dans la barre d’état.
Lorsque le joueur n’a plus de points de vie, la partie est perdue.
Les abeilles n’aiment pas les courants d’air et ne peuvent pas passer sur les portes.
Le joueur ramasse automatiquement les bonus qui se trouvent sur des cellules d’herbe quand il marche dessus.
Les abeilles n’interagissent pas avec les bonus.
Si le joueur ramasse un trognon de pomme pourrie, il devient malade pendant diseaseDuration
secondes et son degré de fatigue diseaseLevel
augmente de 1.
Le degré de fatigue actuel du joueur (diseaseLevel
) est affiché dans la barre d’état.
Si le joueur ramasse des pommes, il gagne un surplus d’énergie de energyBoost
et guérit instantanément (la valeur de diseaseLevel
vaut 1).
Image | Description | Effet |
---|---|---|
![]() |
Herbe | Le joueur consomme 1 * diseaseLevel points d’énergie par déplacement |
![]() |
Terre | Le joueur consomme 2 * diseaseLevel points d’énergie par déplacement |
![]() |
Carottes | Le joueur consomme 3 * diseaseLevel points d’énergie par déplacement |
![]() |
Pomme empoisonnée | Augmente diseaseLevel de 1 pendant une durée de diseaseDuration secondes |
![]() |
Pomme | Réinitialise diseaseLevel à 1 et augmente la quantité d’énergie du joueur de energyBoost |
![]() |
Bosquet de fleurs | Infranchissable pour le joueur, franchissable pour les abeilles |
![]() |
Cœur | Ajoute un point de vie au joueur |
![]() |
Porte fermée | |
![]() |
Porte ouverte | |
![]() |
Clé | Permet d’ouvrir une porte fermée lorsque le joueur marche dessus. Le joueur ne peut marcher sur une porte fermée que s’il possède une clé. |
Nous vous fournissons une première ébauche du jeu.
Vous pouvez consulter le diagrame de classes du projet pour avoir un apperçu des dépendances entre les différentes classes.
Le lancement du jeu fait apparaître une carte minimaliste (MapLevelDefaultStart
), chargée statiquement en mémoire, dans laquelle le joueur peut se déplacer dans toutes les directions, quelle que soit la nature des cellules.
Les paramètres du jeu sont définis dans un fichier de propriétés.
Les fichiers properties en Java permettent de facilement stocker des couples de clés/valeurs.
Les valeurs par défauts sont les suivantes.
Paramètres | Valeur par défaut | |
---|---|---|
playerLives | 5 | |
playerInvincibilityDuration | 4s | |
beeMoveFrequency | 1 / s | |
playerEnergy | 100 | |
energyBoost | 50 | |
energyRecoverDuration | 5s | |
diseaseDuration | 5s |
Nouvelle version du 29/03/2023 à 22:06.
La position du joueur est maintenant extraite depuis la carte.
Les fichiers impactés par les modifications sont GameLauncher.java
, MapLevel.java
, MapLevelDefault.java
, MapLevelDefaultStart.java
ainsi que les deux fichiers de cartes dans le répertoire world
.
Archive du jeu de base : UBGarden-student.zip
Dans cette première version, les abeilles sont immobiles. Nous utiliserons pour le moment la carte par défaut.
MapLevelDefault
à la place de MapLevelDefaultStart
.Dans la version de base, le jeu ne dispose que d’un seul niveau codé en dur dans le code.
Nous allons maintenant charger une configuration complète de jeu depuis un fichier.
Vous trouverez un répertoire world
à la racine du projet avec deux fichiers d’extension
.properties
représentant un monde avec 2 niveaux (avec ou sans compression des niveaux).
Nous utilisons le codage LRE vu en TD avec ou sans compression en fonction du paramètre compression
dans le fichier de configuration.
Le joueur peut perdre une vie s’il se fait piquer par une abeille.
Le joueur bénéficie alors d’une temporisation de playerInvincibilityDurationl
secondes pendant laquelle il est invulnérable.
Si le joueur n’a plus de vie, la partie se termine.
Faire en sorte que l’image du joueur change pendant sa période d’invincibilité (ajoutez un effet à l’image).
La partie est finie lorsque le joueur n’a plus de vie ou s’il arrive à rejoindre la
princesse.
La touche [ESCAPE]
permet de quitter la partie à tout moment.
Lorsque le joueur marche sur un bonus, il faut pourvoir le ramasser. Imaginons que les classes Apple
et PoisonedApple
soient des sous-classes de la classe Bonus
. On aimerait pouvoir écrire dans le code de la classe Player
quelque chose comme ça :
class Player {
void take(Apple apple) { ... }
void take(PoisonedApple poisonedApple) { ... }
void doMove(Direction direction) {
Position nextPos = direction.nextPosition(getPosition());
Decor next = game.world().getGrid().get(nextPos);
setPosition(nextPos);
if (next != null)
take(next);
}
}
Le problème est qu’en Java, on ne peut pas faire un dispatch de méthode dynamique en fonction du type d’un paramètre. Il est possible de faire un dispatch statique (surcharge) ou dynamique (redéfinition et polymorphisme). Pour résoudre le problème, nous allons faire ce qu’on appelle de l’inversion de dépendance.
class Player {
// [...]
void doMove(Direction direction) {
// ...
if (next != null)
next.takenBy(this);
}
}
Plutôt que le joueur ramasse un bonus, on va demander au bonus de se faire ramasser par le joueur. Avec le dispatch dynamique, c’est la bonne méthode takenBy
qui sera invoquée. Il faut ensuite invoqué dans chacune des classes Apple
et PoisonedApple
la bonne méthode de ramassage du joueur. Il s’agit dans ce cas de dispatch statique.
public interface Takeable {
default void takenBy(Player player) {}
}
public abstract class Bonus implements Takeable {}
public class Apple extends Bonus {
public void takenBy(Player player) {
player.take(this);
}
}
public class PoisonedApple extends Bonus {
public void takenBy(Player player) {
player.take(this);
}
}