Premier mini-projet : concevoir un jeu ressemblant à Space Invaders

Space Invaders

Space Invaders est un jeu sorti en 1978 sur borne d'arcade.

Le principe est de piloter un vaisseau qui lutte contre des invasions d'aliens à l'aide d'un canon laser.

INTRODUCTION

  • la création de ce programme a été découpée en plusieurs étapes pour te faciliter les choses

COMMENT FAIRE ?

  • chaque étape te propose un code pré-écrit dans lequel il y a des trous représentés par des pointillés que tu devras compléter
  • copie le code et colle-le dans ton programme dans la bonne section : draw ou update
  • remplace les pointillés par le code manquant. Pour réussir, il faut BIEN LIRE les commentaires et la documentation

L'objectif de ce projet, dans un premier temps, est de créer quelque chose qui ressemble à ce qui suit :


Etape 1 : Coder la fenêtre de notre jeu

C'est exactement la même étape dans les deux mini-projets

RAPPEL IMPORTANT

  • colle le code suivant dans ton éditeur
  • remplace les pointillés en t'aidant des commentaires et de la documentation (que tu peux ouvrir en permanence dans un autre onglet de ton navigateur
import pyxel

# CONSTANTES (des valeurs qui ne changent jamais)
LARGEUR = ...   # largeur de notre fenêtre
HAUTEUR = ...   # hauteur de notre fenêtre
FPS = ...       # nombre d'images par seconde dans notre jeu
NOIR = ...      # voir la documentation pour connaître le nombre correspondant à la couleur noire
BLANC = ...     # faire pareil avec les autres couleurs
VERT = ...
ORANGE = ...
ROUGE = ...
TITRE = ...     # titre de la fenêtre (à mettre entre guillemets)

# VARIABLES (des valeurs qui vont changer au cours du jeu

# taille de la fenetre 128x128 pixels
# penser à utiliser les constantes définies juste au-dessus
pyxel.init(LARGEUR, HAUTEUR, title = ... , fps = ...)

def update():
    pass        # pour l'instant, on ne fait rien dans cette fonction

def draw():

    # effacer la fenêtre et la remplir de couleur noire (voir la documentation pour savoir comment faire)
    # utiliser la constante NOIR définie plus haut
    ...

pyxel.run(update, draw)

A faire dans cet exercice :

  • copier / coller le code ci-dessus dans votre éditeur et faites ce qui suit :
  • dans les parenthèses de pyxel.init, remplacer les ... par les valeurs appropriées pour obtenir une fenêtre de 128 pixels de large sur 128 pixels de haut avec le titre que vous souhaitez et qui se ferme grâce à la touche que vous aurez choisie.
  • dans la fonction draw(), écrire le code qui permet d'effacer le contenu de la fenêtre et de mettre un fond noir. Si besoin, chercher dans la documentation de pyxel
  • exécuter votre programme pour vérifier qu'il fonctionne

Vous pouvez vous aider du cours de ce site et de la documentation en ligne pour répondre à ce premier exercice.

Ci-dessous les codes des couleurs à utiliser dans Pyxel :

Image de la palette de couleurs Pyxel


Etape 2 : Insérer une forme (un sprite) qui représentera notre vaisseau

Pour faire simple, on représentera notre vaisseau par un carré.

Nous pourrons, lors de la prochaine étape, déplacer cette forme, cet élément de notre jeu. Il va donc porter le nom de sprite.

# dans CONSTANTES, on ajoute la ligne suivante : 

TAILLE_VAISSEAU = ...       # taille (arête) de notre sprite (carré)

# dans variables, on ajoute les lignes suivantes

vaisseau_x = ...        # définir la position horizontale du vaisseau au début
vaisseau_y = ...        # définir la position verticale du vaisseau au début

# dans def draw(), on ajoute les lignes suivantes

    # dessiner une forme carrée de couleur ORANGE (notre vaisseau) (voir la documentation ou ci-dessous pour savoir comment compléter pyxel.rect)
    # le carré est positionné aux coordonnées de l'écran vaisseau_x, vaisseau_y et il est de taille TAILLE_VAISSEAU
    pyxel.rect(..., ..., TAILLE_VAISSEAU, TAILLE_VAISSEAU, ...)

On complète notre programme précédent :

  • au début du programme, on détermine les coordonnées x, y pour positionner le vaisseau (vaisseau_x, vaisseau_y).

A vous de choisir. Attention la fenêtre ne fait que 128 sur 128 pixels, il ne faut pas dépasser ces valeurs.

  • dans draw(), nous allons dessiner un carré représentant notre vaisseau. Pour cela nous allons utiliser la fonction pyxel.rect().

Cette fonction est paramétrée de la façon suivante : pyxel.rect(positionx, positiony, largeur, hauteur, couleur) :

  • positionx, positiony : position (horizontale, verticale) du carré
  • largeur : largeur du carré
  • hauteur : hauteur du carré
  • couleur : couleur du carré (à choisir parmi les 16 disponibles)

Etape 3 : Déplacement du vaisseau

Reprenons le programme précédent

A FAIRE

  • On ajoute une constante VITESSE_VAISSEAU qui définira la vitesse de déplacement de notre sprite
  • On modifie la fonction update
  • Complète ton programme en copiant en dessous ce qui manque dans le programme précédent et en complétant les pointillés
# dans CONSTANTES, ajouter la ligne suivante
VITESSE_VAISSEAU = ...

# dans def update(), coller le code suivant A LA PLACE de la ligne pass
    # on doit mettre ce qui suit pour que les variables globales puissent être utilisées dans la fonction update()
    global vaisseau_x, vaisseau_y  

    # mise à jour de la position du sprite en ajoutant la vitesse à la position
    vaisseau_x += ...    

Etape 4 : Gérer les sorties d'écrans

Problème du programme précédent : notre sprite sort de la fenêtre !!!

Corrigeons cela avec une condition.

A FAIRE

  • Une fois encore, on ne modifie que la fonction update
  • Complète ton programme en rajoutant ce qu'il manque et en complétant les pointillés
# dans def update(), modifie ton code avec ce qui suit :     

    # mise à jour de la position du sprite en ajoutant la vitesse à la position
    # si la position du vaisseau reste inférieure à la largeur de la fenêtre moins sa taille, on déplace le 
    # vaisseau
    if vaisseau_x < ... - ... :
            vaisseau_x += ...   

Si tu n'as pas fait d'erreur, le sprite se déplace vers la droite jusqu'au bord de la fenêtre et s'arrête.


Etape 5 : Utilisation du clavier pour changer la direction du vaisseau

Maintenant que tu as compris comment déplacer le sprite, ajoutons du code pour effectuer ce déplacement seulement quand on appuie sur une touche du clavier.

Pour détecter une touche clavier enfoncée, on utilise pyxel.btn() en passant en paramètre la touche sur laquelle on appuie.

Rappel : pour connaître les touches, il faut aller voir à la fin de la documentation.

Par exemple : pyxel.btn(pyxel.KEY_RIGHT) deviendra vrai si on appuie sur la flèche droite du clavier.

# dans def update() modifie ton code pour qu'il ressemble à ce qui suit :

    # si la touche flèche droite du clavier est enfoncée
    if ...:
            # mise à jour de la position du sprite en ajoutant la vitesse à la position
            # si la position du vaisseau ne dépasse pas [largeur de la fenêtre - largeur du sprite], on déplace le vaisseau
        if vaisseau_x < ...- ...:
            vaisseau_x += ... 

Etape 6 : Utilisation du clavier pour déplacer le vaisseau dans l'autre sens

Compléter le code précédent dans def update() avec un déplacement dans l'autre direction (vers la gauche).

Essayer d'améliorer votre programme, pour que le sprite ne sorte pas de la fenêtre.

Conseil

  • Il suffit de faire presque la même chose que pour déplacer le vaisseau vers la gauche
  • Plutôt que d'utiliser if, on utilisera elif pour else if afin d'éviter le cas où l'utilisateur appuie sur les 2 touches en même temps

Il faut donc rajouter le code suivant (en le complétant) à notre update() :

# dans def update(), on rajoute :
    # si la touche flèche gauche du clavier est enfoncée       
    elif pyxel.btn(...):
        # mise à jour de la position du sprite en ajoutant la vitesse à la position
        if vaisseau_x > ... :       # si le vaisseau ne sort pas de l'écran à gauche
            vaisseau_x -= ...                  # on déplace le vaisseau vers la gauche

Etape 7 : Ajouter un canon laser à notre vaisseau

A partir de maintenant, nous voulons que notre vaisseau tire chaque fois que nous appuyons sur la touche Espace.

A FAIRE

  • déclarer deux nouvelles variables (à mettre avec les autres variables) tir_x, tir_y qui donneront la position de notre tir
  • quand on appuie sur la touche espace, un rectangle jaune (représentant le tir) apparaît sur l'écran à la position tir_x, tir_y
  • le tir se déplace vers le haut de la fenêtre et disparaît

Attention

Une fois de plus, dans le code ci-dessous, n'apparaît que ce qu'il faudra ajouter à ton programme par rapport à ce que tu as déjà écrit. ATTENTION !

# dans CONSTANTES, ajouter les lignes suivantes :
LARGEUR_TIR = 1
HAUTEUR_TIR = 4
JAUNE = 10
VITESSE_TIR = 4 # vitesse de déplacement du tir

# dans VARIABLES, ajouter les lignes suivantes :
tir_x = 0    # peu importe les valeurs, elles seront modifiées dès qu'on appuie sur la touche Espace par la suite
tir_y = 0

# dans def update(), ajouter ou modifier les lignes suivantes:
    # on doit mettre ce qui suit pour que les variables globales puissent être utilisées dans la fonction update()
    global vaisseau_x, vaisseau_y, tir_x, tir_y 

    # si la touche espace est enfoncée et relâchée, le tir est créé DEPUIS LA POSITION DU VAISSEAU
    if pyxel.btnr(...):     # btnr parce que le tir en partira que la touche ESPACE est relâchée
        tir_x = ... + TAILLE_VAISSEAU // 2    # pour que le tir parte du milieu du vaisseau
        tir_y = ...                           # pour que le tir parte de la position y du vaisseau

    # le tir se déplace vers le haut de la fenêtre à sa vitesse
    tir_y -= ... 

# dans def draw(), ajouter les lignes suivantes :
    # dessin des tirs : on dessine un rectangle positionné en tir_x, tir_y, de taille largeur_tir par hauteur_tir de couleur JAUNE
    pyxel.rect(tir_x, tir_y, ..., ..., ...)

Révisions

Essaie ton programme une fois complété... Ca marche comme prévu ?

Pas vraiment, non... Si la touche Espace est enfoncée de façon répétée le tir précédent ne va pas jusqu'en haut de la fenêtre parce qu'il est automatiquement remplacé par un nouveau tir.

ATTENTION A partir d'ici, ça se complique !!!

Si tu ne sais pas ce que sait, il faut ABSOLUMENT que tu remontes dans la partie PYTHON DEBUTANT pour comprendre ce qu'est une liste et comment on s'en sert

  • pour obtenir ce que l'on veut, nous allons devoir créer une liste dans laquelle nous allons stocker tous les tirs créés
  • nous dessinerons chaque élément de cette liste (chaque tir donc)
  • nous ferons se déplacer chaque élément de cette liste vers le haut

Dans la partie variable du programme, on remplace tir_x et tir_y par la liste liste_tirs.

# dans VARIABLES, ajouter la ligne suivante
liste_tirs = [] # création d'une liste vide pour stocker les positions des tirs
Puis, pas de devinette cette fois, reprenons ci-dessous update et draw en donnant directement la solution :

# dans def update(), complète ton code déjà écrit pour qu'il ressemble à ce qui suit :

    # si la touche espace est enfoncée et relâchée, le tir est créé
    if pyxel.btnr(...):
        tir_x = ... + TAILLE_SPRITE // 2   # pour que le tir parte du milieu du vaisseau
        tir_y = ...
        liste_tirs.append([tir_x,tir_y]) # on ajoute le tir à la liste défini par sa position horizontale et sa position verticale

    # pour chaque tir de la liste, le tir se déplace vers le haut de la fenêtre (on modifie la position verticale du tir)
    for tir in liste_tirs:
        tir[1] -= ...   # tir[1] désigne le second élément de tir donc la position y - revoir les listes si ce n'est pas clair

# dans def draw(), remplace le code existant par celui-ci:

    # pour chaque tir présent dans la liste, on dessine un rectangle positionné en tir[0], tir[1], de taille largeur_tir par hauteur_tir et de couleur JAUNE
    for tir in liste_tirs:
        pyxel.rect(tir[0], tir[1], ..., ..., ...)
Nous avons donc créé une liste contenant tous les tirs du vaisseau.

Mais une fois que le tir est sorti de l'écran, il reste dans la liste.

Ca ne va pas. Il faut le retirer.

# ajoute les lignes suivantes dans def update():
    # suppression du tir de liste_tirs une fois qu'il est sorti de l'écran
    for tir in liste_tirs:
        if tir[1] < 0:
            liste_tirs.remove(tir)

Etape 8 : Dessiner les aliens sur une première rangée

Pour représenter les aliens, nous allons, de nouveau, dessiner des carrés de largeur TAILLE_VAISSEAU.

Pour ne pas les dessiner et les gérer un par un, nous allons regrouper tous ces aliens dans une liste appelée liste_aliens. Vous savez vous servir des listes, profitons-en !

Rappel

Une fois de plus ici, nous ne mettons que le code à ajouter ou à modifier.

# dans VARIABLES, ajouter la ligne suivante
liste_aliens = []      # on crée une liste vide pour stocker les POSITIONS des aliens

# en dessous de pyxel.init et au-dessus de def update(), ajouter les lignes suivantes

# création des aliens
for x in range(15, LARGEUR - 10, 12):
    liste_aliens.append([x, 10])

# dans def draw(), ajouter les lignes suivantes :

# dessiner les aliens
    for alien in liste_aliens:
        # on dessine des carrés en position alien[0], alien[1], de dimensions TAILLE_VAISSEAU x TAILLE_VAISSEAU et de couleur blanche
        pyxel.rect(alien[0], alien[1], ..., ..., ...)

Etape 9 : Dessiner deux autres rangées d'aliens

Il n'y a qu'une seule rangée d'aliens alors que dans la version originale du jeu, il y a plusieurs rangées.

Nous allons modifier notre programme pour créer trois rangées. Pour cela, remplacer le code existant par ce qui suit

# création des aliens
# on crée une boucle dite imbriquée : on augmente 3 fois y de 10 pour créer 3 rangées
# dans chaque rangée, on crée 10 aliens comme précédemment
for y in range(10,40,10):
    for x in range(10,LARGEUR-10,12):
        liste_aliens.append([x, y])

Etape 10 : Remplacer les carrés (vaisseau et aliens) par des images

Le fichier (la banque d'images) qui contient les sprites est accessible en cliquant sur le lien suivant :

space.pyxres

Il faut le télécharger et le déposer dans le même répertoire que le programme Python. Si tu utilises Pyxel Studio, télécharge le fichier sur ton ordinateur, puis, à l'aide de l'icône fichier en haut à gauche, installe-le dans ton répertoire de travail de Pyxel Studio

A FAIRE

  • télécharger le fichier space.pyxres et le déposer dans le même répertoire que le programme Python
  • dans le programme, charger le fichier à l'aide de pyxel.load
  • remplacer le carré du vaisseau et les carrés des aliens par des images de la banque d'images
# en dessous de pyxel.init on copie la ligne suivante :
pyxel.load("space.pyxres")    # on charge le fichier contenant les différents sprites

# dans def draw():
    # on supprime cette ligne en la commentant par un #
    #pyxel.rect(vaisseau_x, vaisseau_y, TAILLE_VAISSEAU, TAILLE_VAISSEAU, ORANGE)

    # on la remplace par pyxel.blt
    # il faut indiquer la position du vaisseau à la place des pointillés
    # 0,0,0 signifie l'image 0 de la banque positionnée en 0,0 dans la banque de dimension TAILLE_VAISSEAU par TAILLE_VAISSEAU)
    
    pyxel.blt(..., ... , 0, 0 ,0 , ..., ...)

     # dessiner les aliens
        for alien in liste_aliens:
            # on remplace cette ligne
            # pyxel.rect(alien[0], alien[1], TAILLE_VAISSEAU, TAILLE_VAISSEAU, BLANC)  # ajout d'un décalage de 5 à x pour centrer la rangée d'alien
            # par la ligne suivante
            pyxel.blt(alien[0], alien[1] , O, 8 ,O , TAILLE_VAISSEAU, TAILLE_VAISSEAU)

Etape 11 : Affichage du score et du nombre de vies

A FAIRE

  • créer une variable score qu'on initialise à 0 et une variable nombreVies qu'on initialise à 3
  • dans draw(), afficher le score placé en haut à gauche et le nombre de vies en haut à droite à la même hauteur en BLANC
# dans VARIABLES, ajouter la ligne suivante
score = ...
nombreVies = ...


# dans def draw(), ajouter les lignes suivantes
    # afficher le score et du texte en haut à gauche avec la couleur BLANCHE
    # afficher le nombre de vies et du texte en haut à droite avec la couleur BLANCHE
    pyxel.text(..., ..., "Score : " + str(score), ...)
    pyxel.text(..., ..., "Vies : " + str(nombreVies), ...)

Chaque fois qu'un alien est détruit, le score doit être augmenté de 1. A faire dans l'étape suivante


Etape 12 : Collisions entre les tirs du vaisseau et les aliens

Dans cette partie, pas de devinette.

Nous allons donner directement la fonction qui permet de vérifier si deux sprites sont en collision.

Volontairement, c'est une fonction généraliste puisque nous allons l'utiliser pour la collision entre les tirs du vaisseau et les aliens MAIS AUSSI, plus tard, entre les tirs des aliens et le vaisseau.

Copier le code ci-dessous et le coller juste après pyxel.init et pyxel.load

# fonction qui détermine si un objet A de largeurA et hauteurA entre en collision avec un objet B de largeurB et hauteurB
# si les deux objets sont en collision, la fonction renvoie vrai (True)
# sinon elle rennvoire Faux (False)
def collision(A, B, largeurA, hauteurA, largeurB, hauteurB):
    # A : tir / B : alien
    if (B[0] + largeurB > A[0]) and (B[0] < A[0] + largeurA) and \
        (B[1] + hauteurA >  A[1]) and (B[1] < A[1] + hauteurB):
        return True
    else:
        return False

A FAIRE

Si le tir du vaisseau entre en collision avec un alien : * retirer l'alien de la liste * augmenter le score de 1

# dans def update(), 

# 1 - ajouter les lignes suivantes


    # gestion des collisions entre les tirs du vaisseau et les aliens
    for alien in liste_aliens:
        for tir in liste_tirs:
            if collision(tir, alien, LARGEUR_TIR, HAUTEUR_TIR, TAILLE_VAISSEAU, TAILLE_VAISSEAU):
                liste_aliens.remove(alien)
                liste_tirs.remove(tir)
                score += 1

# 2 - pour que cela fonctionne, ajouter score à la liste des variables globales déjà présente pour obtenir la ligne suivante :
     global vaisseau_x, vaisseau_y, tir_x, tir_y, score

En cours de rédaction...