Projet C# – Space Invaders

Transcription

Projet C# – Space Invaders
ESIEE Paris
C# - Space Invaders
Projet 2015
Projet C# – Space Invaders
Etape 1: Comprendre la structure d'un programme animé - TP1
Télécharger le mini-projet Visual C# à cette adresse : http://www.esiee.fr/~perretb/E3FI/SpaceInvaders.zip . Ce
projet contient un squelette fonctionnel d'un petit programme interactif que vous modifierez et complèterez tout au
long du projet.
Avant même d’ouvrir ce projet dans Visual Studio, ajoutez tous les fichiers qu’il contient sur votre dépôt SVN et
réalisez un commit.
Pour le moment, le projet comporte trois classes :



GameForm.cs : c'est la classe d'affichage et d'interaction. Elle représente la fenêtre de jeu.
Game.cs : c'est elle qui contient toute la mécanique du jeu. Elle centralise les différents éléments du jeu, elle
gère leur évolution et leurs interactions.
BalleQuiTombe : c'est un objet de "démonstration", il s'agit d'une balle qui ne fait que tomber. Elle est
capable de calculer l'évolution de sa position en fonction du temps qui passe. Elle sait également se
représenter (dessiner l'image qui la représente).
Game.cs
GameForm.cs
- Gestion des évènements
- Gestion de l'affichage
- Mécanique du jeu :
Gestion du temps
Gestion des objets du jeu
(mouvement, interaction)
- Dessin
Dessin des objets du jeu
Affichage d'information
BalleQuiTombe.cs
- Mouvement et interaction
(position, vitesse, collision)
- Dessin
Questions de compréhension:
 Que faut-il modifier pour que la balle tombe plus lentement ou plus rapidement ?
Travail de programmation:
 Ajouter la gestion des touches "flèche gauche" et "flèche droite". Lorsque le joueur appuie sur une des
flèches, la balle (si elle existe) doit se déplacer dans la direction indiquée tout en continuant à tomber.
Etape 2: Classe utilitaire Vecteur2D - TP1
Afin de faciliter la gestion des coordonnées et des déplacements, il est nécessaire de disposer d'un type permettant
de représenter des points et des vecteurs du plan et de réaliser des opérations basiques (addition, multiplication par
un scalaire, calcul de la norme, ...). Comme .net ne propose pas de structure pour cela (il y a la classe Point mais qui
est en coordonnées entières) on va écrire une classe Vecteur2D.
Travail de programmation:
 Créez une classe Vecteur2D avec deux champs "x" et "y" de type double.
 Créez un constructeur paramétrique permettant d'initialiser les valeurs de x et de y. Mettez la valeur par
défaut 0 sur les deux paramètres.
 Ajoutez une propriété publique "Norme" de type double en lecture seule qui retourne la norme du vecteur.
 Redéfinissez les opérateurs suivants:
Benjamin Perret
Page 1 sur 9
ESIEE Paris
C# - Space Invaders
Opérateur
Vecteur + Vecteur
Vecteur – Vecteur
- Vecteur
Vecteur * double
double * Vecteur
Vecteur / double
Projet 2015
Description
Addition vectorielle
Soustraction vectorielle
Moins unaire
Multiplication par un scalaire à droite
Multiplication par un scalaire à gauche
Division par un scalaire
Etape 3 : La classe Spaceship - TP1
Tous les vaisseaux du jeu, alliés ou ennemis, sont représentés par la même classe : "SpaceShip". La classe SpaceShip
est très simple, car par défaut les vaisseaux ne font rien et ils seront donc "piloter" par une autre entité (le joueur ou
"l'IA"). Un vaisseau dispose d'un certain nombre de vies et n'est détruit que lorsque toutes ses vies lui ont été otées.
Travail de programmation:
 Ecrivez la classe SpaceShip. Cette classe possède les membres suivants:
Membre
Type
Nom
Description
Propriété publique
Vecteur2D
Poisition
Position du vaisseau
Propriété publique
int
Lives
Nombre de vie du vaisseau
Propriété publique en
bool
Alive
Le vaisseau est-il en vie (nombre de vie >0)
lecture seule
Champ publique
Bitmap
image
Image représentant le vaisseau
Constructeur
SpaceShip
Permet d'initialiser la position, le nombre
de vies et l'image du vaisseau.
Méthode publique
void ( Graphics ) Draw
Dessine l'image du vaisseau dans le
graphique donné
Remarque : des images de vaisseaux sont déjà incluses dans le projet SpaceInvaders (Menu Projet -> Propriétés ->
Ressources -> Images). Ces images sont incluses dans l'exécutable généré à partir de la solution et sont accessibles
directement depuis le code (ex: Bitmap image = SpaceInvaders.Properties.Resources.ship3). Pour dessiner une image
« image » à la position « x, y », utilisez la méthode DrawImage de l’objet graphics : g.DrawImage(image, x, y).
Remarque 2 : La position du vaisseau représente son coin supérieur gauche.
Etape 4 : Déplacement du joueur – TP1
Le joueur dispose d'un vaisseau particulier qu'il peut diriger avec les flèches gauche et droite du clavier. Toute la
gestion du joueur sera faite dans la classe Game.
Travail de programmation:
 Ajouter un champ public "playerShip" de type "SpaceShip" à la classe Game.
 Ajouter un champ privé "playerSpeed" de type double qui représente la vitesse de déplacement du joueur
en pixel/seconde.
 Modifier le constructeur de la classe Game de manière à créer un vaisseau avec 5 vies et centré en bas de
l'écran au démarrage du jeu (choisissez une des images de vaisseau pour le représenter).
 Modifier la méthode Draw pour que le vaisseau soit correctement affiché.
Benjamin Perret
Page 2 sur 9
ESIEE Paris

C# - Space Invaders
Projet 2015
Modifier la méthode Update pour qu'un appui sur la touche gauche (resp. droite) déplace le vaisseau sur la
gauche (resp. droite) en respectant la vitesse définie dans le champ "playerSpeed". Assurez-vous que le
vaisseau ne puisse pas sortir de la zone de jeu !
Etape 5 : La classe Missile – TP2
La classe Missile représente un missile ami ou ennemi. Un missile dispose d'un nombre de vies et d'une vitesse fixée
à sa création.
Travail de programmation:
 Ecrivez la classe Missile. Cette classe possède les membres suivants:
Membre
Type
Nom
Description
Propriété publique
Vecteur2D
Position
Position du missile
Propriété publique
Vecteur2D
Vitesse
Vitesse du missile
Propriété publique
int
Lives
Nombre de vies du missile
Propriété publique en
bool
Alive
Le missile est-il en vie (nombre de vies >0)
lecture seule
Champ publique
Bitmap
image
Image représentant le missile
Constructeur
Missile
Permet d'initialiser la position, la vitesse et le
nombre de vies. L'image est toujours l'image
"shoot" disponible dans les ressources du projet.
Méthode publique
void ( Graphics ) Draw
Dessine l'image du missile dans le graphique
donné
Méthode publique
void (double )
Move
Fait évoluer la position du missile en fontion du
temps écoulé (en paramètre) et de sa vitesse
Etape 6 : Tir du joueur – TP2
Lorsque le joueur appuie sur la touche espace, son vaisseau doit tirer un missile sauf si un missile a déjà été tiré et
est toujours en vie.
Travail de programmation:
 Ajouter un champ "playerMissile" de type Missile à la classe Game.
 Modifier la méthode Draw de la classe Game de manière à afficher correctement le missile.
 Modifier la méthode Update de la classe Game:
o Il faut créer un nouveau missile juste au-dessus du vaisseau du joueur si le joueur appuie sur espace
et qu'il n'y actuellement pas de missile.
o Faire avancer le missile.
o Détruire le missile s'il sort de la zone de jeu ou si il n'est plus en vie.
Etape 7 : Gestion de l'option Pause – TP2
La touche p doit permettre de mettre le jeu en pause puis de revenir dans l'état initial.
Travail de programmation:
 Mettre en place le système de machine à état. Un jeu vidéo est un exemple classique de machine à état (ou
automatique fini) qui peut être dans différents états de jeu qui conduiront à des comportements différents
(en jeu, en pause, menu de départ, ...). On va représenter les différents états du jeu par une énumération
(lire la ressource http://www.esiee.fr/~perretb/E3FI/Enumeration.pdf). Déclarer une énumération
GameState contenant les valeurs "Play" et "Pause" dans la classe Game (d'autres états seront ajoutés par la
Benjamin Perret
Page 3 sur 9
ESIEE Paris
C# - Space Invaders
Projet 2015
suite) et ajouter un champ de type GameState qui représentera l'état courant. Modifiez la fonction Draw de
manière à afficher le texte "Pause" si le jeu est en pause et la balle si le jeu est dans l'état Play. Modifiez la
fonction update de manière à gérer ce nouvel état :
o Si le jeu est dans l'état Play et que le joueur appuie sur la touche p, il doit passer en état pause et le
chronomètre doit être arrêté.
o Si le jeu est dans l'état Pause et que le joueur appuie sur la touche p, le jeu doit redémarrer.
p
Play
Pause
p
Affiche et anime les éléments du
jeu.
Affiche le message "pause".
Le déroulement du temps est stoppé
Etape 8 : La classe Bunker – TP3
Les bunkers permettent au joueur de se mettre à couvert mais peuvent être détruit par les missiles. Les bunkers sont
fixes.
Travail de programmation:
 Ecrivez la classe Bunker. Cette classe possède les membres suivants:
Membre
Type
Nom
Description
Propriété publique
Vecteur2D
Poisition
Position du bunker
Champ publique
Bitmap
image
Image représentant le bunker
Constructeur
Bunker
Permet d'initialiser la position du bunker. L'image
est toujours l'image "bunker" disponible dans les
ressources du projet.
Méthode publique
void ( Graphics ) Draw
Dessine l'image du bunker dans le graphique
donné
Méthode publique
bool (Missile )
Collision
Test la collision (cf. ci-dessous) avec un missile et
détruit une partie du bunker en cas de collision.
Test de collision avec un missile: Pour des raisons de performances, le test est effectué en 2 temps. On commence
par tester si le rectangle englobant du bunker intersecte le rectangle englobant du missile. Si ce n'est pas le cas, on
sait qu'il n'y a pas de collision possible, sinon il faut tester plus précisément.

Test des rectangles englobant :
o les rectangles englobants sont disjoints : aucune collision possible:
o
les rectangles englobants s'intersectent : on ne peut pas conclure:
Benjamin Perret
Page 4 sur 9
ESIEE Paris
C# - Space Invaders
Projet 2015
Pour tester si deux rectangles s'intersectent, on peut vérifier la condition suivante. On considère qu'un
rectangle est paramétré de la façon suivante:
on dispose de deux rectangles (x1,y1,lx1,ly1) et (x2,y2,lx2,ly2), les deux rectangles sont disjoints si
o x2 > x1 + lx1 : le deuxième rectangle est à droite du premier OU
o y2 > y1 + ly1 : le deuxième rectangle est haut dessus du premier OU
o x1 > x2 + lx2 : le premier rectangle est à droite du deuxième OU
o y1 > y2 + ly2 : le premier rectangle est haut dessus du deuxième

Si les rectangles englobant s'intersectent, il faut tester pixel par pixel si il y a une intersection. On parcourt
l'ensemble des pixels du missile et on calcule la position correspondante sur le bunker (on connait la position
du missile et du bunker par rapport au coin supérieur gauche de la fenêtre, et on connait les coordonnées du
pixel par rapport à la position du missile, on peut donc en déduire les coordonnées du pixel par rapport à la
position du bunker). Si le pixel de l'image du bunker qui correspond est noir, il y a collision au niveau de ce
pixel. On supprime alors l'ensemble des pixels en collisions du bunker et on diminue le nombre de vie du
missile de 1.
repère missile
repère écran
repère bunker
Il faut pouvoir passer de coordonnées
dans le repère "missile" aux
coordonnées dans le repère "bunker"
sachant que l'on connait la position
des repères "missile" et "bunker" par
rapport au repère "écran".
Le changement de repère, lorsque les axes sont alignés, est une opération mathématique très
simple. Imaginons que l'on dispose de 2 repères. On connait la position du deuxième repère par
rapport au premier (Ox,Oy). On connait la position d'un point P dans le second repère (Px',Py') et on
souhaite obtenir les coordonnées de P dans le premier repère (Px,Py). On a directement Px=Px'+Ox
et Py=Py'+Oy .
Benjamin Perret
Page 5 sur 9
ESIEE Paris
C# - Space Invaders
Projet 2015
Remarque : les images fournies avec le projet possèdent une couche « alpha » pour gérer la transparence.
Les pixels noirs de l’image correspondent à la couleur (255,0,0,0) : du noir (0 pour les 3 composantes rouge,
verte et bleue) opaque (255 sur la composante alpha) alors que les pixels en dehors du bunker sont
représentés par la couleur (0,255,255,255) : du blanc (255 pour les 3 composantes rouge, verte et bleue)
transparent (0 sur la composante alpha).
Etape 9 : Intégration des bunkers – TP3
Il faut maintenant ajouter la gestion des bunkers dans la classe Game.
Travail de programmation:
 Ajoutez un champ "bunkers" de type List<Bunker> à la classe Game.
 Modifier le constructeur de Game pour qu'il crée automatiquement 3 ou 4 bunkers également repartis sur
une ligne au-dessus de la position du joueur et les ajoute à la liste des bunkers.
 Modifier la méthode Draw de la classe Game de manière à afficher correctement les bunkers présents dans
liste.
 Modifier la méthode Update de la classe Game : il faut tester si le missile du joueur est collision avec le
bunker.
Etape 10 : Bloc d'ennemis, construction et déplacement – TP4
Les ennemis de Space Invaders ont un comportement assez particulier : ils arrivent par ligne de vaisseaux identiques
et ils se déplacent de manière synchronisée. Ainsi, dès qu'un vaisseau atteint le bord de l'écran, c'est l'ensemble des
ennemis qui descendent et inversent leur direction de déplacement horizontale. En fait on peut observer que c'est le
rectangle englobant de l'ensemble des vaisseaux qui importe.
Travail de programmation:
 Ecriver la classe EnnemyBlock. Cette classe possède les membres suivants:
Membre
Type
Nom
Description
Propriété publique
List<SpaceShip>
Ships
Liste des vaisseaux ennemis
Champ publique
Vector
position
Coordonnées du coin supérieur gauche
Champ publique
Size
size
Taille du rectangle englobant les vaisseaux
Benjamin Perret
Page 6 sur 9
ESIEE Paris
C# - Space Invaders
Propriété publique en
lecture seule
Champ privé
Champ privé
Constructeur publique
bool
Méthode publique
void( int width, int
nbShips, int lives,
Bitmap im)
Méthode publique
Méthode publique
void (Graphics)
void(double deltaT)
Projet 2015
Alive
Vector
Vector
Vraie si il y a au moins un vaisseau vivant dans le
bloc.
speedX
Vitesse de déplacement latérale
speedY
Vitesse de déplacement horizontale
EnnemyBlock Initialise un nouveau block à une position
donnée. A ce moment le bloc est vide, sa taille est
donc nulle.
AddLine
Ajoute une ligne de vaisseaux en bas du bloc. La
ligne est composée de nbShips vaisseaux ayant
lives vies, l'image im et également répartis sur la
largeur width.
Draw
Dessine tous les vaisseaux du bloc.
Move
Déplace tous les vaisseaux de speedX. Si le
rectangle englobant des vaisseaux arrive au bord
de la zone de jeu alors il faut : 1) inverser speedX,
2) déplacer tous les vaisseaux de speedy, 3)
augmenter le module de speedX.
Etape 11 : Intégration du bloc d'ennemis – TP4
Il faut maintenant ajouter la gestion des ennemis dans la classe Game.
Travail de programmation:
 Ajouter un champ public de type EnnemyBlock à la classe Game.
 Modifier le constructeur de Game, pour initialiser le bloc d'ennemis (ajouter au moins 4 lignes d'ennemis).
 Modifier la méthode Draw de Game pour afficher le bloc d'ennemis.
 Modifier la méthode Update de Game pour gérer le déplacement du bloc d'ennemis.
Etape 12 : Bloc d'ennemis : jeu gagné et jeu perdu ! – TP4
Maintenant que l'on dispose de la notion d'ennemis (on ne peut pas encore les détruire mais cela va venir !), on peut
ajouter les états de jeu "Perdu" et "Gagné".
Travail de programmation:
 Ajouter les valeurs "Lost" et "Win" à l'énumération GameState
 Modifier la méthode Draw de la classe Game pour afficher un message indiquant le résultat lorsque l'état du
jeu est Win ou Lost.
 Modifier la méthode Update de la classe Game pour gérer les changements d'états. Le jeu doit passer dans
l'état "Lost" si le bloc d'ennemis a atteint le niveau du joueur ou si le nombre de vie du joueur est inférieur
ou égal à zéro. Le jeu doit passer dans l'état Win si le bloc d'ennemis est vide.
p
Play
Nombre
d'ennemis = 0
Win
Benjamin Perret
Pause
p
Nombre de vies = 0 OU
Les ennemis ont atteint le niveau du joueur
Lost
Page 7 sur 9
ESIEE Paris
C# - Space Invaders
Projet 2015
Etape 13 : Bloc d'ennemis : collisions - TP5
Nous allons maintenant ajouter les méthodes nécessaires pour pouvoir détruire les ennemis. Il faut pour cela :
implémenter les collisions entre un vaisseau et un missile et ajouter les codes de gestions.
Travail de programmation:
 Ajouter une méthode public bool Collision(Missile s) à la classe SpaceShip. Cette méthode est assez proche
de la méthode Collision de la classe Bunker mais cette fois, l'image du vaisseau n'est pas modifiée en cas de
collision. En cas de collision, les nombres de vies du vaisseau et du missile sont diminués du minimum entre
le nombre de vie du vaisseau et du missile. Donc, si le vaisseau possède plus de vie que le missile il n'est pas
détruit et l'inverse est également vrai !
 Ajouter une méthode private void UpdateBBox() à la classe EnnemyBlock qui permet de recalculer la
position et la dimension du bloc d'ennemis en fonction des vaisseaux ennemis présents dans le bloc. Cette
fonction sera utilisée pour mettre à jour ces valeurs après la destruction d'un vaisseau.
 Ajouter une méthode public bool Collision(Missile s) à la classe EnnemyBlock. Cette méthode effectue le test
de collision entre le missile et les ennemis du bloc. Si un ennemi est détruit il est supprimé du bloc et les
dimensions du bloc sont mises à jour.
 Modifier la méthode Update de la classe Game de manière à effectuer les tests de collision entre le missile
du joueur et le bloc d'ennemis. Si le missile est détruit lors de la collision il faut le supprimer.
Etape 14 : Bloc d'ennemis : attaques – TP5
Dans Space Invaders, les ennemis tirent aléatoirement. Un tir ennemi peut endommager un bunker ou le vaisseau du
joueur mais pas un autre vaisseau ennemi. Plus le bloc de vaisseau se rapproche du joueur, plus la fréquence de tir
est élevée.
Travail de programmation:
 Ajouter un champ de type List<Missile> dans la classe Game et initialiser ce champs avec une liste vide.
 Modifier la méthode Draw de la classe Game de manière à dessiner les missiles dans la liste.
 Modifier la méthode Update de la classe Game et ajouter:
o le déplacement des missiles de la liste;
o la collision des missiles avec les bunkers et le vaisseau du joueur;
o la suppression d'un missile de la liste si son nombre de vies vaut 0 ou si il sort de l'écran.
 Ajouter un champ ShootProbability de type double à la classe EnnemyBloc. Ce champ représente la
probabilité qu'un vaisseau tire pendant un intervalle de temps. Par exemple, une valeur de 0,1 signifie que
chaque vaisseau tire en moyenne 0,1 fois par seconde, c'est-à-dire 1 fois toutes les 10 secondes. Cette
probabilité doit augmenter à chaque fois que le bloc d'ennemis descend.
 Ajouter une méthode void RandomShoot(Ship ship, double deltaT) à la classe EnnemyBloc qui a pour rôle de
générer aléatoirement un tire pour le vaisseau passé en paramètre en fonction du temps écoulé deltaT et la
probabilité de tir ShootProbability. On procède de la manière suivante :
o On tire au hasard un nombre entre 0 et 1 avec la classe Random
o Si ce nombre est inférieur à deltaT multiplié par ShootProbability alors on crée un objet Missile sous
le vaisseau concerné et on l'ajoute à la liste des missiles de l'objet Game.
 Modifier la méthode Update de la classe EnnemyBloc pour appeler la méthode RandomShoot sur chacun des
vaisseaux du bloc.
Benjamin Perret
Page 8 sur 9
ESIEE Paris
C# - Space Invaders
Etape 15 : Addons
Ajoutez des addons (cf. sujet de projet)
Benjamin Perret
Page 9 sur 9
Projet 2015