TD4 : Gauntlet

L’objectif de ce TD est d’étudier la gestion des collisions dans un environnement complexe. Nous étudierons un jeu de labyrinthe type Gauntlet célèbre franchise des années 90. Vous pourrez retrouver sa version 3D Slayer Edition sortie en 2014.

Le jeu Gauntlet original 1987

Les bornes d’arcade de ce jeu se vendent aujourd’hui très chères, si vous avez un talent de bricoleur, cela peut être très rentable :

../_images/arcade.jpg

Mise en place

Téléchargez le projet Gauntlet.py et ouvrez le.

Au lancement du programme, vous obtenez ceci :

../_images/demo2.gif

Le projet est incomplet. Quelques remarques :

  • La fenêtre de jeu est trop petite

  • Le personnage se déplace uniquement verticalement

  • Le héros ne collisionne pas avec les murs

  • Il sort de l’écran et disparaît…

Rappels Python

Nous rappelons la syntaxe des principaux containers de donnée en Python.

Liste

L = ['titi', 5, 'toto']
print(L)        >> [' titi', 5, 'toto']
print(L[0])     >> ‘titi’
L[1] += 1
L[2] = 'tata'
print(L)        >> ['titi',  6,  'tata']
len(L)          >> 3

Tableau 2D

import numpy as np
T = np.zeros((2,2))
T[0,0] = 1
T[0,1] = 2
T[1,1] = 3
print(T)         >> array([[ 1., 2.], [ 0., 3.]])
T.shape[0]      >> 2

Dictionnaire

D = { }
D['mami']  = 76
D['mami']  += 1
print(D['mami'])   >> 77
D['papi']  = 74
print(D)           >> {'mami': 77, 'papi': 74}

Différence

Pourquoi des listes et des tableaux ? Une liste, en Python, peut contenir des éléments de types différents : nombres, texte, sprites, listes… C’est un objet Python, il n’est pas optimisé et donc plutôt lent.

Le tableau Numpy est issu d’une librairie C recompilé pour Python. Il peut avoir une à plusieurs dimensions mais tous ces éléments doivent être du même type et il ne peut stocker que des types de base : entier, flottant, caractère. En contrepartie, il est très performant.

Il existe aussi le container dictionnaire. Il se comporte comme une liste sauf qu’il peut être indexé sur tout type de donnée comme notamment des strings : D[“age_du_capitaine”] = 44 :

En résumé :

  • J’ai plusieurs dimensions à gérer, les éléments sont de même type >> Tableau

  • J’ai une dimension indexée sur des string >> Dictionnaire

  • Dans les autres cas, une liste devrait convenir

Initialisation

On utilise différentes syntaxes :

L = ['titi', 5, 'toto']
T = np.zeros((2,2))   # init à 0
T = np.ones((2,2))    # init à 1
T = np.empty((2,2))   # allocation sans init
D = { key1:value1, key2:value2, key3:value3}

Notions clefs

Plan

Pour simplifier les choses, nous stockons le plan du labyrinthe dans une liste de strings :

../_images/plan.png

Cela nous permet de représenter très simplement le plan du labyrinthe dans le code.

Couleurs

Dans ce projet, chaque lettre va coder une couleur. Pour cela, nous créons un dictionnaire nommé palette :

../_images/code1.png

Ainsi, palette[“B”] correspond à la couleur bleu et palette[” “] à la couleur noire.

Grâce à ce choix, nous pouvons convertir le tableau plan vers un tableau 3 dimensions contenant les couleurs de chaque pixel en utilisant le code suivant :

../_images/remplissage.png

Ainsi, LABY[x,y] désigne la couleur de la case [x,y] du labyrinthe.

Sprite du personnage

Certains d’entre vous n’aiment pas dessiner ou manipuler des images. Nous proposons ici un autre système de représentation basé sur le pixel-art. Chaque sprite est ainsi dessiné en utilisant une liste de string, chaque lettre codant la couleur d’un pixel. Le mécanisme est simple et permet de dessiner plein d’objets assez facilement :

../_images/perso.png

La fonction ToSprite(…) permet de convertir une liste de string en sprite pour Pygame. Son principe est identique à celui de la construction du tableau LABY vu ci-dessus.

To-do List

  • Faites en sorte que l’aventurier se déplace grâce aux flèches du clavier gauche et droite

  • Utilisez le deuxième sprite de l’aventurier. Deux fois par seconde, alternez les deux sprites pour donner l’impression que le personnage marche en permanence

    • Indice : int(pygame.time.get_ticks()/500) augmente de 1 toutes les 500ms

    • La fonction modulo % devrait vous aider

  • Créez un sprite trésor (clef, porte..) qui symbolise la sortie, affichez le dans le jeu sur une case vide

  • Lorsque la distance entre la sortie et le sprite de l’aventurier est inférieure à 5 pixels, affichez « WIN » en gros au milieu de l’écran

    • Pour calculer une distance entre deux points : d( (x1,y1) , (x2,y2) ) ² = (x1-x2)² + (y1-y2)²

  • Notre aventurier a tendance à traverser les murs. Ce n’est pas acceptable ! Lorsqu’il avance vers la droite, testez sa collision avec le mur en regardant la couleur du pixel à sa droite :

    ../_images/collision.png
    • Le sprite est symbolisé par un rectangle vert

    • Nous regardons la couleur du pixel devant le sprite à mi-hauteur

    • Si la couleur est noire, le joueur peut avancer, si la couleur est bleue, cela veut dire que le joueur est face à un mur et qu’il ne doit pas bouger dans cette direction

    • On doit lire la couleur du pixel après avoir dessiné le labyrinthe, pas avant !

    • La fonction screen.get_at((x,y)) retourne la couleur du pixel de l’écran

    • Les crochets [0] [1] et [2] permettent de récupérer les composantes R,V,B de la couleur

    • Les fonctions xxx.get_width() et xxx.get_height() donnent les dimensions d’un sprite

    • Le pixel de test doit être placé quelques pixels en avant du sprite et ne doit pas être collé à lui. En effet, si le sprite avance de 4 pixels par 4 pixels, il faut positionner le pixel de test 4 pixels devant le sprite

  • Gérez les collisions dans les 4 directions de déplacement

  • Créez un labyrinthe 20x20

    • Vous avez le choix entre diminuer la taille des cases par deux ou doubler la taille de la fenêtre de jeu

    • Pensez à créer un parcours complexe avec plusieurs chemins possibles et des emplacements pour des pièges. La sortie n’est pas forcément en bas à droite !

  • Créez un sprite piège : mine, trappe, flamme, bombe… Positionnez le dans le jeu

  • Lorsque le joueur passe sur le piège, remettez-le à la position de départ

  • L’animation de la marche a tendance à clignoter, en effet, nous utilisons seulement deux sprites, c’est insuffisant

    • Créez de nouveaux sprites pour l’animation de la marche pour améliorer le jeu de jambe du personnage

    • Modifiez la boucle d’animation en conséquence.

  • Créez un sprite trésor (coffre, gemme, pièce d’or…)

    • Ajoutez plusieurs trésors dans le parcours

    • Lorsque le joueur passe à proximité d’un trésor, celui-ci disparaît et le joueur gagne 100 points

  • Affichez le score en haut de l’écran

  • Créez un sprite clef et un sprite porte

    • Ajoutez la clef dans le labyrinthe

    • Positionnez le sprite porte au niveau de la sortie

    • Lorsque le joueur passe à proximité de la clef, elle disparaît et apparaît à coté du score pour indiquer qu’elle ait maintenant dans son inventaire

    • La sortie ne s’active pas si le héros n’a pas ramassé la clef

  • Faites en sorte de positionner plusieurs pièges