Projet : programmation évènementielle

Coder un jeu arkanoïd.

Le but de ce projet est que vous créiez un jeu de type Arkanoïd. De nombreuses parties d'Arkanoïd existent sur youtube, comme par exemple :

https://www.youtube.com/watch?v=Th-Z6QQ5AOQ

Une partie du jeu est visible. Vous devriez avoir les connaissances nécessaire en pyglet pour vous en sortir.

Pour ne pas passer des heures à coder sans résultats, découpez votre programme en plusieurs parties, toutes réalisables en environ 1/2 heure.

Prenez le temps de modéliser proprement vos objets et classes pour que tout votre programme ne soit qu'une imbrication de classes qui interagissent les unes avec les autres.

Pour chaque classe il faudra bien sur ses tests qui permettent de certifier que les méthodes fonctionnent. Il faut faire en sorte que chaque méthode soit testable sans pyglet, juste en simulant une partie.

Mise en place

  1. créez un dossier nommé Arkanoid où vous placerez vos fichiers
  2. créez un projet vscode dans ce dossier
  3. vérifiez que pyglet est bien installé en tentant d'importer pyglet dans un fichier

Entités du projet

A priori, les objets dont vous aurez besoin sont :

Lister les différentes classes et événements que vous devrez gérer pour mener à bien le projet. et essayer de construire un premier jet du modèle UML du projet. Ce modèle n'a pas besoin d'être précis, il doit servir de guide à votre découpage en tâche.

Découpage du projet en tâche

Commencez par découper le projet en classes et déterminez leurs attributs et méthodes. Une fois ceci fait, décrivez votre 1ère tâche : quelle fonctionnalité du jeu est la plus simple à créer tout en ayant un jeu fonctionnel (même incomplet)

Chaque tâche doit correspondre à une fonctionnalité que vous ajoutez à votre jeu. Cette tâche doit :

  • être constituée de méthodes et ou attributs à ajouter à une ou plusieurs classes
  • aux tests de ces méthodes
  • à l'ajoute de cette fonctionnalité au programme

Pour que votre première tâche ne soit pas "faire un jeu Arkanoïd" on ajoute les contraintes :

La fonctionnalité doit pouvoir être ajoutée en 1/2 heure.

Enfin :

On vous demande de garder dans un fichier Markdown les différentes taches que vous avez effectuées avec votre projet.

Si vous suivez ce principe, toutes les 1/2 heure votre jeu sera plus complet. Ne passez pas 1 heure à coder quelque chose sans l'utiliser !

Déroulé

Conservez en plus de votre projet un fichier Markdown dans le quel vous décrirez chaque tâche que vous voulez implémenter, ainsi que l'heure de début et l'heure de fin de la tâche. Si vous voyez que votre tâche prend trop de temps à être créer, scindez votre tâche en tâches plus petites. Au bout de quelques tâches vous devriez être rodé.

Au début de chaque étape :

  1. demandez l'aval de votre encadrant avant de commencer l'implémentation de la tâche
  2. conservez dans le fichier markdown de votre projet la fonctionnalité que vous allez ajouter

Pour débuter le projet, si vous n'avez pas d'idée de déroulé, vous pouvez suivre celui ci-après. Il vous mènera jusqu'au début de l'implémentation des briques.

Ici nous créerons une classe Fenetre qui contiendra notre interface. On ne testera pas l'interface (donc la classe Fenetre), mais tout le reste devra être testé.

Tâche 1

Création d'une fenêtre de 640x480 non redimensionnable avec :

fenêtre

Vous créerez une classe Fenetre héritant de pyglet.window.Window contenant les différents contenus. Votre programme principal consistera à créer un objet de la classe Fenetre et d'exécuter pyglet avec la commande : pyglet.app.run().

À la fin de la tâche 1 vous devriez avoir deux fichiers :

Le diagramme UML de la classe Fenêtre devrait être quelque chose du genre :

fenêtre

Le fichier main.py ne bougera plus de tout le projet et doit être :

import pyglet

from fenêtre import Fenêtre

window = Fenêtre()

print("Ça commence.")
pyglet.app.run()
print("C'est fini !")

Tâche 2

fenêtre aire de jeu

Le diagramme UML de la classe Fenêtre devrait maintenant être quelque chose du genre :

fenêtre

Tâche 3

Faire déplacer le vaisseau de gauche à droite sans cogner les bords.

Comme la gestion des déplacement doit être interne au vaisseau, il faut qu'il puisse avoir sa classe à lui. On va donc réaliser cette tâche en 2 temps.

Tache 3.1

Création de la classe :

Les tests du vaisseau seront fait dans le fichier Arkanoid/test_vaisseau.py. Pour commencer les tests de cette classe, vous pourrez vérifier que le vaisseau est bien initialement placé au centre de la fenêtre en comparant :

fenêtre

Tâche 3.2

Ajout des méthodes et des attributs permettant de déplacer le vaisseau.

Il nous faut côté Fenetre :

  1. modifier la classe fenêtre pour qu'elle prenne en compte les touches "flèche gauche" et "flèche droite" en modifiant un nouveal attribut direction qui vaudra -1 lorsque la touche gauche est appuyée ; 1 lorsque c'est la touche droite et 0 sinon
  2. que l'on change la position du vaisseau à au plus 60 fps (tous les 1/60 secondes)

On doit gérer les mouvement côté Vaisseau en ajoutant :

Pour tester la méthode bouge vous pourrez faire 4 tests :

Pour faire passer ces tests, vous pourrez modifier à la main les différents paramètres comme la vitesse (vaisseau.vitesse) ou la position du vaisseau (vaisseau.forme.x) pour que votre test soit facile à écrire.

Félicitations, votre programme doit pouvoir faire bouger le vaisseau ! Vérifiez le.

fenêtre

Tâche 4

Création et gestion de la bille :

Pour réaliser tout ça on va travailler par morceaux.

Tache 4.1

Création d'une bille et de ses bornes de jeu. Pour cela, il nous faut commencer à créer une classe bille avec un cercle comme dessin.

Création de la classe :

Les tests de la bille seront fait dans le fichier Arkanoid/test_bille.py. Testez que la position initiale de la bille est bien correcte.

Ajoutez la bille à l'interface :

bille

fenêtre

Tâche 4.2

Ajoutons une première version du déplacement de la bille :

Les tests de la bille seront fait dans le fichier Arkanoid/test_bille.py. Pour tester la méthode bouge, vous pourrez faire 1 test test_bouge() qui vérifie que pour dt=2 et une vitesse (2, 5), le déplacement de la bille est bien correct. N'hésitez pas à changer directement les attributs dans votre test.

Ajoutez la bille dans le jeu, avec une vitesse initiale de (0, -100)

fenêtre

Tâche 4.3

Ajoutons le fait que si la bille sort de la fenêtre par le bas (sa position y est inférieure à la hauteur du sol), elle réapparait au milieu de l'écran avec la même vitesse.

Testez cette fonctionnalité.

Tâche 4.4

Faite maintenant rebondir la bille sur les murs. Cela peut se faire en changeant la direction d'une des coordonnées du vecteur vitesse (cette coordonnée change selon le mur) dans la méthode bouge de la bille si celle-ci devait dépasser le mur.

Testez ces fonctionnalités avec un test par mur en :

  1. vous plaçant juste avant l'impact
  2. effectuez une méthode update
  3. vérifier que la vitesse de la bille a bien changé et que sa position est bien à nouveau dans les bornes de la fenêtre.

Tâche 5

Le vaisseau doit se comporter comme un mur lorsque la bille le touche. Pour cela il faut gérer les collisions entre éléments du jeu.

Nous allons gérer ceci en plusieurs temps.

Tâche 5.1

Ajoutez une méthode collision(bille) au vaisseau. Cette méthode doit répondre True si la bille touche le vaisseau, et False sinon. Pour cela vous pouvez utiliser l'algorithme suivant, qui regarde s'il y a collision entre un disque de centre $(x0, y0)$ et de rayon $R$ avec un rectangle dont le somment en bas à gauche est en $(x1, y1)$ :

def collision_disque_rectangle(x0, y0, rayon, x1, y1, hauteur, largeur):
        dist_x = min(
            (x0 - x1) ** 2,
            (x0 - x1 - largeur) ** 2,
        )
        dist_y = min(
            (y0 - y1) ** 2,
            (y0 - y1 - hauteur) ** 2,
        )

        if (x1 <= x0 <= x1 + largeur) and (
            y1 <= y0 <= y1 + hauteur
        ):
            return True
        elif x1 <= x0 <= x1 + largeur:
            return dist_y < rayon ** 2
        elif y1 <= y0 <= y1 + hauteur:
            return dist_x < rayon ** 2
        else:
            return dist_x + dist_y < rayon ** 2

Testez cette méthode.

fenêtre

Tâche 5.2

Après la mise à jour des position dans la méthode update, testez s'il y a collision entre la bille et le vaisseau. Si oui, faite rebondir la bille.

Tâche 6

Gestion de la vie et du score.

Tâche 6.1

Lorsqu'il y a collision entre le vaisseau et la bille, le score augmente de 1.

Tâche 6.2

Lorsque la la bille tombe dans le sol, on l'a faite réapparaitre au milieu de l'écran avec la même vitesse (tâche 4.3).

Pour gérer facilement la vie vous pouvez :

Tâche 7

Une brique doit se comporter comme le vaisseau lorsque la bille la touche (elle rebondit), puis disparaître. Commençons par dessiner les briques

Création de la classe :

Dans Fenetre créez une liste self.briques et ajoutez y un mur de briques à notre interface. On pourra créer 5 rangées de 12 briques, par exemple.

fenêtre

Tâche 8

Pour finir, il reste à mettre en place la gestion des collisions entre a bille et les briques. Il n'y a presque rien à faire car tout va se passer comme pour le vaisseau.

Ajoutez une méthode collision(bille) à la classe Brique. Cette méthode doit répondre True si la bille touche la brique, et False sinon (il faut que le centre de la bille soit dans le rectangle du vaisseau augmenté du rayon de la bille, tout comme pour le vaisseau).

Après la mise à jour des positions dans la méthode update, testez s'il y a collision entre la bille et une brique. Si oui, augmentez le score de 5, faite rebondir la bille et supprimez cette brique de la liste des briques.

fenêtre