Projet - Space Invaders

Le but de ce projet est de reproduire un des grands classiques du jeu vidéo : Space Invaders ! Space Invaders est un shoot’em up où le principe est de détruire des vagues d’aliens au moyen d’un canon en se déplaçant horizontalement sur l’écran.

Travail demandé

L’évaluation porte sur le travail personnel de l’étudiant et s’appuie sur deux livrables:

  • le code source du logiciel développé,

  • le rapport de projet.

Code source

Le code source du programme devra répondre au cahier des charges fonctionnel et aux critères de qualité de code énoncés dans les sections suivantes.

Rapport

Le rapport fera au maximum 5 pages. Il devra contenir :

  • une description succincte du problème,

  • une description de la structure de votre programme permettant de comprendre son fonctionnement,

  • une présentation des problèmes soulevés par les tests : analyse de l’origine du problème, de la solution trouvée ou des idées de solutions (si le problème n’a pas été résolu).

Evaluation

Le barême ci-dessous est donné à titre indicatif et peut évoluer.

Le rapport est noté sur 20 : les éléments de notations sont:

  • la qualité de la rédaction (y compris présentation, orthographe et grammaire)

  • la présentation du sujet

  • l’explication de la solution et

  • l’analyse des problèmes rencontrés.

La notation du code rendu diffère suivant le parcours choisi (voir fin de la section Démarrage):

  • Piste verte et piste bleue:

    • 10 points d’évaluation fonctionnelle: le programme fonctionne-t-il comme demandé dans le cahier des charges ?

    • 5 points pour la qualité du code : le code du programme respecte-t-il les recommandations données dans la section Qualité de code ?

    • 5 points pour les fonctionnalités supplémentaires implémentées (voir section Addons): uniquement si tous les éléments demandés dans le cahier des charges ont été implémentés.

  • Piste noire: la piste noire étant significativement plus complexe que les 2 autres, il n’est pas nécessaire d’ajouter des éléments non demandés dans le cahier des charges pour atteindre la note de 20 :

    • 15 points d’évaluation fonctionnelle: le programme fonctionne-t-il comme demandé dans le cahier des charges ?

    • 5 points pour la qualité de code: le code du programme respecte-t-il les recommandations données dans la section Qualité de code ?

La note de projet est composée pour 1/4 de la note de rapport et pour 3/4 de la note du code rendu.

Modalité de rendu

Votre dêpot Git (voir ci-dessous) devra contenir les éléments suivants:

  • le code source de votre projet,

  • votre rapport (format pdf).

Le contenu de votre dépôt Git sera téléchargé à la fin de la 2ème période école (approximativement fin du mois de novembre, date exacte précisée par mail) et constituera votre rendu de projet.

Il est fortement recommandé de réaliser un clone de votre dépôt avant la date limite et sur une autre machine que celle que vous avez utilisée pendant vos développements afin de vérifier que celui-ci est complet, que votre projet compile et s’exécute correctement. Tout évènement menant à un retard de rendu (par exemple dépôt Git incomplet à la date limite) entrainera une pénalité de \(2^{n-1}\) points (avec \(n\) le nombre de jours de retard).

Important

Plagiat : La récupération de tout ou portion du travail d’une tierce personne, qu’il s’agisse d’un étudiant, d’un site internet, d’une vidéo ou tout autre support, n’est permise qu’avec la permission de l’auteur, ce dernier devant être cité dans le code et dans le rapport. Le travail utilisé avec citation des sources n’est pas considéré comme le résultat du travail de l’étudiant et ne peut donc pas apporté de point lors de la notation. Un travail utilisé sans référence à son auteur constitue un plagiat. Le plagiat est interdit par le règlement intérieur de l’école (Article 3). En cas de plagiat, le conseil de discipline peut prononcer des sanctions allant jusqu’à l’exclusion définitive de l’étudiant.

Démarrage

Prise en main de Git

Les informations vous permettant de créer un dépôt Github pré-initialisé et rattaché à cette unité vous serons envoyées par mail.

Si vous ne connaissez pas Git, suivez le guide: Git .

Comprendre la structure d’un programme animé

Votre dépôt Git contient déjà un squelette fonctionnel d’un programme interactif que vous modifierez et complèterez.

Pour le moment, le projet comporte trois classes importantes :

  • Game.cs : c’est elle qui contient les mécanismes fondamentaux du jeu.

  • GameObject.cs : c’est une entité abstraite pour représenter un objet actif du jeux.

  • BalleQuiTombe.cs : c’est un objet de « démonstration » qui hérite de GameObject, 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).

../_images/cd1.png

Démarrer le jeux et appuyez sur la touche Espace : trouvez les portions de code qui sont responsables des actions suivantes:

  • Création de l’objet BalleQuiTombe lors de l’appui sur espace.

  • Déplacement de l’objet vers le bas.

  • Destruction de l’objet lorsqu’il arrive en bas de la fenêtre.

Questions de compréhension:

  • Que faut-il modifier pour que la balle tombe plus lentement ou plus rapidement?

  • Ajoutez la gestion des touches « flèche gauche » et « flèche droite » dans la méthode Update de la classe BalleQuiTombe. Lorsque le joueur appuie sur une des flèches, la (ou les) balle qui existe doit se déplacer dans la direction indiquée tout en continuant à tomber.

Classe utilitaire Vecteur2D

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…). Comme .net ne propose pas de structure pour cela (il y a la classe Point mais qui est en coordonnées entières) nous allons écrire une classe Vecteur2D:

  • 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:

    • Vecteur2D + Vecteur2D: Addition vectorielle

    • Vecteur2D - Vecteur2D: Soustraction vectorielle

    • -Vecteur2D : Moins unaire

    • Vecteur2D * double: Multiplication par un scalaire à droite

    • double * Vecteur2D: Multiplication par un scalaire à gauche

    • Vecteur2D / double : Division par un scalaire

Suite

Vous poursuivez l’implémentation selon le cahier des charges donnés ci-dessous avec 3 parcours possibles:

  • 👶 Piste verte : Vous débutez en programmation orientée objet; suivez le guide étape par étape Space Invaders - Piste Verte .

  • 😎 Piste bleue : Vous vous sentez à l’aise en programmation orientée objet; structurez vous-même votre logiciel.

  • 🐱‍👤 Piste noire : Vous maitrisez la programmation orientée objet; pour un peu de défi essayez-vous au modèle Entité-Composant-Système .

Pour les pistes bleues et noires, des éléments d’aides spécifiques sont donnés dans la dernière section de cette page.

Cahier des charges fonctionnel

Éléments fondamentaux

L’interface est composée des éléments suivants:

  • le vaisseau du joueur;

  • une rangée de bunker;

  • les vaisseaux ennemis;

  • les missiles;

  • les vaisseaux, les bunkers et les missiles sont représentés à l’écran par des sprites/bitmap.

  • le nombre de vies restantes du joueur.

../_images/ihm.svg

Déplacement du joueur :

  • Le vaisseau du joueur se déplace uniquement horizontalement.

  • Le vaisseau du joueur ne peut pas sortir de l’écran.

  • Le déplacement se fait au moyen des touches flèche gauche et flèche droite.

Bloc d’ennemis :

  • Les ennemis sont organisés en un bloc.

  • Le bloc est constitué de plusieurs lignes d’ennemis.

  • Chaque ligne d’ennemis comprend un ou plusieurs vaisseaux du même type.

  • Le bloc arrive par le haut de la fenêtre.

  • Le bloc se déplace alternativement de gauche à droite et de droite à gauche.

  • Le bloc change de direction dès qu’un des vaisseau du bloc arrive au bord de l’écran.

  • Lorsque le bloc change de direction, il se décale vers le bas.

  • Lorsque le bloc change de direction, sa vitesse de déplacement augmente.

Missiles :

  • Les missiles se déplacent en ligne droite verticale: de haut en bas si tiré par un ennemis et de bas en haut si c’est un missile du joueur.

  • Les missiles des ennemis peuvent entrer en collision avec les bunkers, le vaisseau du joueur et le missile du joueur (pas de friendly fire).

  • Les missiles du joueur peuvent entrer en collision avec les bunkers, les vaisseaux ennemis et les missiles des ennemis.

  • Les missiles sont détruits si ils sortent de l’écran.

  • Le joueur tire avec la touche espace.

  • Les ennemis tirent aléatoirement.

  • La fréquence de tir des ennemis augmente lorsqu’ils se rapprochent du bas de l’écran.

  • Un vaisseau ne peut tirer qu’un seul missile à la fois: lorsqu’il tire il doit attendre que le missile soit détruit par une collision ou sorte de l’écran avant de pouvoir tirer à nouveau.

Vie et mort :

  • Le joueur, les ennemis et les missiles possèdent chacun un nombre de vies.

  • Un élément dont le nombre de vie est inférieur ou égal à zéro est détruit.

Collision missile-vaisseau :

  • Un missile est en collision avec un vaisseau si un pixel noir de son sprite est sur un pixel noir du sprite du vaisseau.

  • Lorsqu’un missile touche un vaisseau, les nombres de vies du missile et du vaisseau sont décrémentés de la valeur minimum entre le nombre de vies du missile et du vaisseau.

  • Tous les pixels en collision sont retirés du sprite du vaisseau: ils deviennent transparents et ne peuvent plus participer à des collisions.

Collision missile-bunker :

  • Un missile est en collision avec un bunker si un pixel noir de son sprite est sur un pixel noir du sprite du vaisseau: un tel pixel est dit en collision.

  • Tous les pixels en collision sont retirés du sprite du bunker: ils deviennent transparents et ne peuvent plus participer à des collisions.

  • Le nombre de vie du missile est décrémenté du nombre de pixels en collision.

Collision missile-missile :

  • Deux missiles qui entre en collision se détruisent mutuellement (ne dépent pas du nombre de vie des missiles).

Victoire et défaite, la première des conditions suivantes qui survient met fin à la partie:

  • Le joueur gagne si il détruit tous les vaisseaux ennemis.

  • Le joueur perd si il est détruit.

  • Le joueur perd si le bloc d’ennemis atteint la hauteur du joueur.

  • A la fin de la partie, le joueur peut relancer une nouvelle partie en appuyant sur Espace

Pause :

  • Tant que la partie n’est pas terminée le joueur peut mettre le jeu en pause: pendant la pause tous les déplacements et donc toute la dynamique du jeu est interrompue.

  • En cours de jeu, le joueur passe ne pause avec la touche P.

  • Lorsque le jeu est en pause, le joueur peut quitter la pause avec la même touche P.

Addons

Les addons seront pris en compte uniquement si tous les éléments fondamentaux fonctionnent. Les addons ne sont pas complètements spécifiés et vous êtes libres de développer autour des thèmes proposés ou de proposer de nouveaux thèmes.

Bonus :

  • La destruction d’un vaisseau ennemis a une probabilité de faire apparaitre un bonus.

  • Le bonus est représenté par un sprite et de déplace du haut vers le bas.

  • Si le vaisseau du joueur entre en collision avec le bonus il le récupère.

  • Les bonus peuvent être de différentes natures: invincibilité temporaire, missile indestructible, missile auto-guidé, tire multiple, vie bonusldots

Affichage avancé :

  • Les sprites des différents éléments sont animés.

  • Les actions de collisions génèrent des effets visuels (par exemple, explosion).

Son :

  • Le jeu est accompagné d’une musique en fond sonore.

  • Les différentes actions produisent l’émission de sons spécifiques.

Système de niveaux :

  • Le jeu comporte plusieurs niveaux.

  • Les niveaux sont codés dans des fichiers textes éditables.

  • Le jeu propose un système de high score.

Multi-joueurs :

  • Plusieurs joueurs peuvent jouer simultanément sur la même machine.

  • Plusieurs joueurs peuvent jouer simultanément en réseau.

Qualité de code

Environnement technique :

  • Le logiciel doit être compilable et exécutable sous la forme d’une solution Visual Studio sur les machines de TP de l’école.

  • Le logiciel doit être intégralement écrit en C#.

Structuration :

  • Le logiciel doit être structuré en utilisant les techniques de programmation orientée objets.

  • Les éléments de langages vus en cours doivent être privilégié dès que cela est pertinent.

  • Chaque classe doit correspondre à un groupe de composants bien identifié.

  • Le rôle de chaque composant doit être clairement identifié.

  • Tout ce qui correspond au rôle d’un composant doit être regroupé dans la classe le représentant.

Norme de codage :

  • Le code doit être correctement formaté selon les conventions de codage recommandées pour le C#

  • Une fonction ne doit pas contenir plus de 10 lignes de codes.

  • On respectera le principe DRY: la duplication de code est proscrite.

Explications et commentaires :

  • Tous les identificateurs, à l’exception des variables de boucles, doivent avoir un nom explicite qui permet de comprendre leur utilité.

  • Toutes les méthodes doivent être documentées: rôle de la méthode, description des paramètres, description de la valeur de retour (voir les conventions officielles ).

Gestion de sources :

  • Le dépôt sur Github ne devra pas contenir de résultats de compilation (fichiers exécutables, fichiers objets, informations de débogageldots).

Éléments de spécification détaillés et aide

Gestions des sprites

Des images de vaisseaux/bunker/missile 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:

Bitmap image = SpaceInvaders.Properties.Resources.ship3;

Pour dessiner une image image à la position positionX, positionY, utilisez la méthode DrawImage de l’objet graphics.

g.DrawImage(image, positionX, positionY, image.Width, image.Height);

Diagramme d’état du jeu

Le jeu peut être dans 4 états différents: en cours, en pause, gagné, perdu. Le diagramme d’état indiquant les transitions possibles est le suivant:

../_images/etats.svg

Test de collision

Le test de collision entre 2 objets (missile/bunker ou missile/vaisseau) fait intervenir le calcul de l’intersection entre deux sprites situés à des positions différentes. Pour des raisons de performances, le test est effectué en 2 temps. On commence par tester si le rectangle englobant du bunker/vaisseau 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 englobants

  • les rectangles englobants sont disjoints; aucune collision possible:

    ../_images/testcol1.png
  • les rectangles englobant s’intersectent; on ne peut pas conclure:

    ../_images/testcol2.png

Pour tester si deux rectangles s’intersectent, on considère qu’un rectangle est paramétré de la façon suivante:

../_images/rectangle.png

Si on dispose de deux rectangles \((x_1,y_1,lx_1,ly_1)\) et \((x_2,y_2,lx_2,ly_2)\), les deux rectangles sont disjoints si

  • \(x_2 > x_1 + lx_1\) : le deuxième rectangle est à droite du premier OU

  • \(y_2 > y_1 + ly_1\) : le deuxième rectangle est haut dessus du premier OU

  • \(x_1 > x_2 + lx_2\) : le premier rectangle est à droite du deuxième OU

  • \(y_1 > y_2 + ly_2\) : le premier rectangle est haut dessus du deuxième.

Test pixel à pixel

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/vaisseau (on connait la position du missile et du bunker/vaisseau 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/vaisseau). Si le pixel de l’image du bunker/vaisseau qui correspond est noir, il y a collision au niveau de ce pixel.

../_images/reperes.png

Le changement de repère, lorsque les axes sont alignés, est une opération simple. Imaginons que l’on dispose de 2 repères. On connait la position du deuxième repère par rapport au premier \((O_x,O_y)\). On connait la position d’un point \(P\) dans le second repère \((P_{x'},P_{y'})\) et on souhaite obtenir les coordonnées de \(P\) dans le premier repère \((P_x,P_y)\). On a directement \(P_x=P_{x'}+O_x\) et \(P_y=P_{y'}+O_y\).

../_images/repere.png

Dans le cas qui nous intéresse il sera nécessaire d’effectuer deux changements de repères consécutifs: 1) du repère missile vers le repère écran, et 2) du repère écran vers le repère vaisseau/bunker.

Gestion des couleurs

Les images fournies avec le projet possèdent une couche emph{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/vaisseau/missile 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).