Aller au contenu

Prévisualisation du niveau

La sélection de niveau est fonctionnelle mais très peu ergonomique : les noms des niveaux ne sont pas très parlants. On va donc faire en sorte que le niveau sélectionné soit affiché en arrière plan du menu.

Refactoring

On commence par un peu de refactoring. On modifie des fonctions de l'affichage du niveau pour pouvoir les réutiliser dans le menu. L'idée est de ne plus leur passer l'état en paramètre, mais directement ce qu'on veut afficher.

Par exemple

def rendre_personnage(etat):
    ecran = etat.rendu.ecran
    position = etat.simulation.personnage.position
    pygame.draw.circle(ecran, COULEUR_PERSONNAGE, ref_sve(position), TAILLE_PERSONNAGE)

devient

def rendre_personnage(ecran, personnage):
    position = personnage.position
    pygame.draw.circle(ecran, COULEUR_PERSONNAGE, ref_sve(position), TAILLE_PERSONNAGE)

On fait de même pour les deux autres fonctions :

def rendre_projectiles(ecran, projectiles):
    for p in projectiles:
        position = p.position
        pygame.draw.circle(ecran, COULEUR_PROJECTILES, ref_sve(position), p.taille)
def rendre_chronometre(ecran, decompte_victoire, caracteres):
    chrono = f"{decompte_victoire:06.2f}" #Conversion en chaine

    x = MARGES_CHRONOMETRE #x de départ (référentiel écran)
    y = MARGES_CHRONOMETRE #y de départ (référentiel écran)

    for c in chrono:
        ecran.blit(caracteres[c], (x, y))
        #la methode get_width de l'objet image renvoie la largeur de l'image.
        x += caracteres[c].get_width()

On adapte finalement la fonction d'affichage.

def afficher_rendu(etat, t):
    ecran = etat.rendu.ecran
    ecran.fill(COULEUR_ARRIERE_PLAN)

    rendre_chronometre(etat.rendu.ecran, etat.simulation.decompte_victoire, etat.rendu.caracteres_chrono)

    rendre_fin_de_partie(etat)

    rendre_projectiles(etat.rendu.ecran, etat.simulation.projectiles)

    rendre_personnage(etat.rendu.ecran, etat.simulation.personnage)

    pygame.display.flip()  

Chargement caractères menu

Dans la classe du Menu, on charge les caractères du chronomètres comme on l'avait fait pour la classe Rendu.

class Menu:
    def __init__(self, niveaux, ecran):
        # Ces 4 booléens sont mis a True 
        # si le joueur a lancé la commande dans la frame
        self.haut = False 
        self.bas = False 
        self.lancer = False 
        self.quitter = False
        self.niveaux = niveaux
        #indice du niveau sélectionné.dans la liste des niveaux
        self.niveau_selectionne = 0 
        self.ecran = ecran

        self.textes_niveaux = {}

        fonte = pygame.font.SysFont("Mono", TAILLE_MENU, True, False)
        for n in niveaux:
            self.textes_niveaux[n.nom] = fonte.render(n.nom, True, COULEUR_MENU) 

        fonte_chrono = pygame.font.SysFont("Mono", TAILLE_CHRONOMETRE, True, False)
        self.caracteres_chrono = images_caracteres(fonte_chrono, "0123456789.", COULEUR_CHRONOMETRE)

On écrit une fonction rendre_niveau_menu, qui effectue un rendu de la prévisualisation du niveau, en appelant les trois fonctions adaptées plus haut sur le niveau actuellement sélectionné.

def rendre_niveau_menu(menu):
    ecran = menu.ecran
    niveau = menu.niveaux[menu.niveau_selectionne]

    rendre_chronometre(ecran, niveau.decompte_victoire, menu.caracteres_chrono)

    rendre_projectiles(ecran, niveau.projectiles)

    rendre_personnage(ecran, niveau.personnage)

Et on l'appelle dans la fonction d'affichage du menu, juste après avoir effacé l'écran.

def afficher_menu(menu):
    ecran = menu.ecran
    ecran.fill(COULEUR_ARRIERE_PLAN)

    rendre_niveau_menu(menu)

    n = menu.niveau_selectionne

    rendre_nom_niveau(menu, n, 0, 255)

    # On dessine les niveaux adjacents si besoin,
    # en ajoutant un décalage de position
    if n > 1:
        rendre_nom_niveau(menu, n - 2, -ESPACEMENT_MENU*2, 255 - GAIN_TRANSPARENCE_MENU * 2)

    if n > 0:
        rendre_nom_niveau(menu, n - 1, -ESPACEMENT_MENU, 255 - GAIN_TRANSPARENCE_MENU)

    if n < len(menu.niveaux) - 1:
        rendre_nom_niveau(menu, n + 1, ESPACEMENT_MENU, 255 - GAIN_TRANSPARENCE_MENU)

    if n < len(menu.niveaux) - 2:
        rendre_nom_niveau(menu, n +2, ESPACEMENT_MENU*2, 255 - GAIN_TRANSPARENCE_MENU * 2)

    pygame.display.flip()

Si on lance le jeu, on peut maintenant voir le niveau sélectionné en arrière plan dans le menu.

Arriere plan menu

Arriere plan menu

Le code complet à ce point de l'étape se trouve dans le dépliant ci-dessous

Code Complet
import pygame
from pygame.math import Vector2
import os

####################
####   CONFIG   ####
####################

## Général 

NIVEAU = "niveau"

FPS = 60

LARGEUR_FENETRE = 1280
HAUTEUR_FENETRE = 720

## Controles

TOUCHE_QUITTER = pygame.K_ESCAPE
TOUCHE_HAUT = pygame.K_UP  
TOUCHE_BAS = pygame.K_DOWN 
TOUCHE_DROITE = pygame.K_RIGHT 
TOUCHE_GAUCHE = pygame.K_LEFT
TOUCHE_RECOMMENCER = pygame.K_r

## Simulation

VITESSE_PERSONNAGE = 300
TAILLE_PERSONNAGE = 10
DECOMPTE_VICTOIRE = 5.0

## Rendu

COULEUR_ARRIERE_PLAN = (0, 0, 0)
COULEUR_PERSONNAGE = (255, 146, 205)
COULEUR_PROJECTILES = (146, 255, 205)
COULEUR_CHRONOMETRE = (150, 150, 170)
TAILLE_CHRONOMETRE = 30
MARGES_CHRONOMETRE = 20
TAILLE_FIN_DE_PARTIE = 150
COULEUR_FIN_DE_PARTIE = (150, 150, 170)

## Menu

TAILLE_MENU = 50
COULEUR_MENU = (255, 255, 255)
ESPACEMENT_MENU = 120
GAIN_TRANSPARENCE_MENU = 100

####################
####  GENERAL   ####
####################

class GameState :

    def __init__(self):
        self.controles = None # on met a None parce que ces attributs seront initialisés plus tard
        self.simulation = None
        self.rendu = None

####################
#### CONTROLEUR ####
####################

class Controles :
    def __init__(self):
        self.quitter = False
        self.direction = Vector2(0,0)
        self.recommencer = False

def initialiser_controles(etat):
    etat.controles = Controles()

def reinitialiser_controles(etat):
    etat.controles = Controles()

def traiter_controles(etat, t):

    for e in pygame.event.get():
            if e.type == pygame.KEYDOWN: 
                if e.key == TOUCHE_QUITTER:
                    etat.controles.quitter = True
                if e.key == TOUCHE_RECOMMENCER:
                    etat.controles.recommencer = True

    direction = Vector2(0, 0)

    clavier = pygame.key.get_pressed()
    if clavier[TOUCHE_HAUT]:
        direction.y += 1.0
    if clavier[TOUCHE_BAS]:
        direction.y -= 1.0
    if clavier[TOUCHE_DROITE]:
        direction.x += 1.0
    if clavier[TOUCHE_GAUCHE]:
        direction.x -= 1.0

    if direction.length() != 0:
        direction.normalize_ip()

    etat.controles.direction = direction

####################
#### SIMULATION ####
####################

class Personnage:

    def __init__(self, position):
        self.position = position

def copier_personnage(personnage):
    return Personnage(personnage.position.copy())

class Projectile: 

    def __init__(self, position, taille, direction, vitesse):
        self.position = position
        self.direction = direction
        self.taille = taille
        if self.direction.length != 0:
            self.direction.normalize_ip()
        self.vitesse = vitesse

def copier_projectile(projectile):
    return Projectile(projectile.position.copy(), projectile.taille, projectile.direction.copy(), projectile.vitesse)

def copier_projectiles(projectiles):
    copie = []
    for p in projectiles:
        copie.append(copier_projectile(p))
    return copie

class Niveau:
    def __init__(self, nom, personnage, decompte_victoire, projectiles):
        self.nom = nom
        self.personnage = personnage
        self.decompte_victoire = decompte_victoire
        self.projectiles = projectiles

class Simulation:

    def __init__(self, niveau):
        self.personnage = copier_personnage(niveau.personnage)
        self.projectiles = copier_projectiles(niveau.projectiles)
        self.collision = False
        self.decompte_victoire = niveau.decompte_victoire
        self.attente = True

def initialiser_simulation(etat, niveau):
    etat.simulation = Simulation(niveau)

def reinitialiser_simulation(etat, niveau):
    initialiser_simulation(etat, niveau)

def intersection(c1, r1, c2, r2):
    return c1.distance_to(c2) <= r1 + r2

def detecter_collisions(etat):
    perso = etat.simulation.personnage
    for p in etat.simulation.projectiles:
        if intersection(perso.position, TAILLE_PERSONNAGE, p.position, p.taille):
            etat.simulation.collision = True

def deplacer_personnage(etat, t):
    direction = etat.controles.direction
    etat.simulation.personnage.position += direction * VITESSE_PERSONNAGE * t

def limiter_deplacement_personnage(etat):
    p = etat.simulation.personnage
    if p.position.x < TAILLE_PERSONNAGE:
        p.position.x = TAILLE_PERSONNAGE
    if p.position.x > LARGEUR_FENETRE - TAILLE_PERSONNAGE:
        p.position.x = LARGEUR_FENETRE - TAILLE_PERSONNAGE
    if p.position.y < TAILLE_PERSONNAGE:
        p.position.y = TAILLE_PERSONNAGE
    if p.position.y > HAUTEUR_FENETRE - TAILLE_PERSONNAGE:
        p.position.y = HAUTEUR_FENETRE - TAILLE_PERSONNAGE

def deplacer_projectiles(etat, t):
    for p in etat.simulation.projectiles:
        p.position += p.direction * p.vitesse * t

def faire_rebondir_projectiles(etat):
    for p in etat.simulation.projectiles:
        if p.position.x < p.taille:
            p.position.x = p.taille
            p.direction.x *= -1
        if p.position.x > LARGEUR_FENETRE - p.taille:
            p.position.x = LARGEUR_FENETRE - p.taille
            p.direction.x *= -1
        if p.position.y < p.taille:
            p.position.y = p.taille
            p.direction.y *= -1
        if p.position.y > HAUTEUR_FENETRE - p.taille:
            p.position.y = HAUTEUR_FENETRE - p.taille
            p.direction.y *= -1

def decompter_victoire(etat, t):
    etat.simulation.decompte_victoire -= t
    if etat.simulation.decompte_victoire < 0:
        etat.simulation.decompte_victoire = 0.0

def verifier_attente(etat):
    if etat.simulation.attente and etat.controles.direction.length() != 0.0:
        etat.simulation.attente = False

def avancer_simulation(etat, t):
    if etat.simulation.attente:
        verifier_attente(etat)
    elif not etat.simulation.collision and etat.simulation.decompte_victoire != 0.0:
        deplacer_personnage(etat, t)
        limiter_deplacement_personnage(etat)
        deplacer_projectiles(etat, t)
        faire_rebondir_projectiles(etat)
        detecter_collisions(etat)
        decompter_victoire(etat, t)

####################
####   RENDU    ####
####################

def images_caracteres(fonte, caracteres, couleur):
    cars = {}
    for c in caracteres: 
        cars[c] = fonte.render(c, True, couleur)
    return cars

class Rendu:
    def __init__(self, ecran):
        self.ecran = ecran

        fonte_chrono = pygame.font.SysFont("Mono", TAILLE_CHRONOMETRE, True, False)
        self.caracteres_chrono = images_caracteres(fonte_chrono, "0123456789.", COULEUR_CHRONOMETRE)

        fonte_fin = pygame.font.SysFont("Mono", TAILLE_FIN_DE_PARTIE, True, False)
        self.texte_victoire = fonte_fin.render("VICTOIRE", True, COULEUR_FIN_DE_PARTIE)
        self.texte_defaite = fonte_fin.render("DEFAITE", True, COULEUR_FIN_DE_PARTIE)

def ref_sve(v):
    return Vector2(v.x, HAUTEUR_FENETRE - v.y)

def initialiser_rendu(etat, ecran):
    etat.rendu = Rendu(ecran)

def reinitialiser_rendu(etat):
    pass

def rendre_personnage(ecran, personnage):
    position = personnage.position
    pygame.draw.circle(ecran, COULEUR_PERSONNAGE, ref_sve(position), TAILLE_PERSONNAGE)

def rendre_projectiles(ecran, projectiles):
    for p in projectiles:
        position = p.position
        pygame.draw.circle(ecran, COULEUR_PROJECTILES, ref_sve(position), p.taille)

def rendre_chronometre(ecran, decompte_victoire, caracteres):
    chrono = f"{decompte_victoire:06.2f}" #Conversion en chaine

    x = MARGES_CHRONOMETRE #x de départ (référentiel écran)
    y = MARGES_CHRONOMETRE #y de départ (référentiel écran)

    for c in chrono:
        ecran.blit(caracteres[c], (x, y))
        #la methode get_width de l'objet image renvoie la largeur de l'image.
        x += caracteres[c].get_width()

def rendre_image_centree(ecran, image):
    centre_ecran = Vector2(LARGEUR_FENETRE/2, HAUTEUR_FENETRE/2) 
    centre_image = Vector2(image.get_width()/2, image.get_height()/2)
    ecran.blit(image, centre_ecran - centre_image)

def rendre_fin_de_partie(etat):
    ecran = etat.rendu.ecran
    if etat.simulation.collision : 
        #si on a perdu
        rendre_image_centree(ecran, etat.rendu.texte_defaite)
    elif etat.simulation.decompte_victoire == 0.0:
        #si la partie est finie et qu'on a pas perdu
        rendre_image_centree(ecran, etat.rendu.texte_victoire)

def afficher_rendu(etat, t):
    ecran = etat.rendu.ecran
    ecran.fill(COULEUR_ARRIERE_PLAN)

    rendre_chronometre(etat.rendu.ecran, etat.simulation.decompte_victoire, etat.rendu.caracteres_chrono)

    rendre_fin_de_partie(etat)

    rendre_projectiles(etat.rendu.ecran, etat.simulation.projectiles)

    rendre_personnage(etat.rendu.ecran, etat.simulation.personnage)

    pygame.display.flip()  


####################
####   NOYAU    ####
####################

def chemin_vers_niveau(niveau):
    repertoire = os.path.dirname(__file__)
    fichier = niveau + ".lasero" #on ajoute l'extension
    return os.path.join(repertoire, fichier)

def analyse_vecteur(chaine):
    sans_parentheses = chaine.replace("(", "").replace(")", "")
    coords = sans_parentheses.split(",")
    x = float(coords[0])
    y = float(coords[1])
    return Vector2(x, y)

def analyse_personnage(chaine):
    return Personnage(analyse_vecteur(chaine))

def analyse_decompte_victoire(chaine):
    return float(chaine)

def analyse_projectile(chaine):
    separee = chaine.split(" ")
    position = analyse_vecteur(separee[0])
    taille = float(separee[1])
    direction = analyse_vecteur(separee[2])
    vitesse = float(separee[3])
    return Projectile(position, taille, direction, vitesse)

def charger_niveau(nom):

    with open(chemin_vers_niveau(nom), "r") as f:
        lignes = f.readlines() 

    personnage = analyse_personnage(lignes[0])
    decompte_victoire = analyse_decompte_victoire(lignes[1])
    projectiles = []

    for l in lignes[2:]:
        projectiles.append(analyse_projectile(l))

    return Niveau(nom, personnage, decompte_victoire, projectiles)

def lister_niveaux():
    entrees = os.listdir(os.path.dirname(__file__)) 
    fichiers_niveaux = [f for f in entrees if ".lasero" in f]
    noms_niveaux = []
    for f in fichiers_niveaux:
        noms_niveaux.append(f.replace(".lasero", ""))
    return noms_niveaux

def charger_niveaux():
    noms_niveaux = lister_niveaux()
    niveaux=[]
    for n in noms_niveaux:
        niveaux.append(charger_niveau(n))
    return niveaux

def jouer_niveau(niveau, ecran):
    etat = GameState()

    initialiser_controles(etat)
    initialiser_simulation(etat, niveau)
    initialiser_rendu(etat, ecran)

    clock = pygame.time.Clock()

    while not etat.controles.quitter:

        if etat.controles.recommencer:
            reinitialiser_controles(etat)
            reinitialiser_simulation(etat, niveau)
            reinitialiser_rendu(etat)

        t = clock.tick(FPS)/1000

        traiter_controles(etat, t)

        avancer_simulation(etat,t)

        afficher_rendu(etat, t)

####################
####    MENU    ####
####################

class Menu:
    def __init__(self, niveaux, ecran):
        # Ces 4 booléens sont mis a True 
        # si le joueur a lancé la commande dans la frame
        self.haut = False 
        self.bas = False 
        self.lancer = False 
        self.quitter = False
        self.niveaux = niveaux
        #indice du niveau sélectionné.dans la liste des niveaux
        self.niveau_selectionne = 0 
        self.ecran = ecran

        self.textes_niveaux = {}

        fonte = pygame.font.SysFont("Mono", TAILLE_MENU, True, False)
        for n in niveaux:
            self.textes_niveaux[n.nom] = fonte.render(n.nom, True, COULEUR_MENU) 

        fonte_chrono = pygame.font.SysFont("Mono", TAILLE_CHRONOMETRE, True, False)
        self.caracteres_chrono = images_caracteres(fonte_chrono, "0123456789.", COULEUR_CHRONOMETRE)

def traiter_controles_menu(menu):
    ## on remet à zéro les contrôles pour ne pas reprendre
    ## l'état de la frame précédente
    menu.haut = False 
    menu.bas = False 
    menu.lancer = False 
    menu.quitter = False

    for e in pygame.event.get():
            if e.type == pygame.KEYDOWN: 
                if e.key == TOUCHE_QUITTER:
                    menu.quitter = True
                if e.key == TOUCHE_RECOMMENCER:
                    menu.lancer = True
                if e.key == TOUCHE_HAUT:
                    menu.haut = True
                if e.key == TOUCHE_BAS:
                    menu.bas = True

def selectionner_niveau_menu(menu):
    if menu.haut:
        menu.niveau_selectionne -= 1 #on remonte dans la liste
    if menu.bas:
        menu.niveau_selectionne += 1 #on descends dans la liste

    #on vérifie qu'on ne sort pas de la liste
    if menu.niveau_selectionne < 0:
        menu.niveau_selectionne = 0
    if menu.niveau_selectionne > len(menu.niveaux) - 1:
        menu.niveau_selectionne = len(menu.niveaux) - 1

def lancer_niveau_menu(menu):
    if menu.lancer:
        jouer_niveau(menu.niveaux[menu.niveau_selectionne], menu.ecran)

def rendre_image_centree_decalee(ecran, image, decalage):
    centre_ecran = Vector2(LARGEUR_FENETRE/2, HAUTEUR_FENETRE/2) 
    centre_image = Vector2(image.get_width()/2, image.get_height()/2)
    ecran.blit(image, centre_ecran - centre_image + decalage)

def rendre_nom_niveau(menu, i, decalage, transparence):
    ecran = menu.ecran
    texte = menu.textes_niveaux[menu.niveaux[i].nom]
    texte.set_alpha(transparence) #on décide de la transparence
    rendre_image_centree_decalee(ecran, texte, Vector2(0, decalage))

def rendre_niveau_menu(menu):
    ecran = menu.ecran
    niveau = menu.niveaux[menu.niveau_selectionne]

    rendre_chronometre(ecran, niveau.decompte_victoire, menu.caracteres_chrono)

    rendre_projectiles(ecran, niveau.projectiles)

    rendre_personnage(ecran, niveau.personnage)

def afficher_menu(menu):
    ecran = menu.ecran
    ecran.fill(COULEUR_ARRIERE_PLAN)

    rendre_niveau_menu(menu)

    n = menu.niveau_selectionne

    rendre_nom_niveau(menu, n, 0, 255)

    # On dessine les niveaux adjacents si besoin,
    # en ajoutant un décalage de position
    if n > 1:
        rendre_nom_niveau(menu, n - 2, -ESPACEMENT_MENU*2, 255 - GAIN_TRANSPARENCE_MENU * 2)

    if n > 0:
        rendre_nom_niveau(menu, n - 1, -ESPACEMENT_MENU, 255 - GAIN_TRANSPARENCE_MENU)

    if n < len(menu.niveaux) - 1:
        rendre_nom_niveau(menu, n + 1, ESPACEMENT_MENU, 255 - GAIN_TRANSPARENCE_MENU)

    if n < len(menu.niveaux) - 2:
        rendre_nom_niveau(menu, n +2, ESPACEMENT_MENU*2, 255 - GAIN_TRANSPARENCE_MENU * 2)

    pygame.display.flip()

def lancer_menu(niveaux, ecran):
    menu = Menu(niveaux, ecran)

    clock = pygame.time.Clock()


    while not menu.quitter:
        clock.tick(FPS)

        traiter_controles_menu(menu)

        selectionner_niveau_menu(menu)

        lancer_niveau_menu(menu)

        afficher_menu(menu)

####################
####    MAIN    ####
####################

def main():
    pygame.init()

    fenetre = pygame.display.set_mode([LARGEUR_FENETRE, HAUTEUR_FENETRE])

    niveaux = charger_niveaux()

    lancer_menu(niveaux, fenetre)

    pygame.quit()

if __name__ == "__main__":
    main()

Lisibilité

L'affichage de certains niveaux en arrière plan rends peu lisible le menu. Pour palier ce problème on va ajouter un "brouillard" gris partiellement transparent entre le menu et la pévisualisation du niveau, de manière à griser la prévisualisation.

On ne peut pas dessiner la transparence directement sur l'écran, a la place, on va la dessiner sur une image, à l'aide de la classe Surface, et ensuite seulement la dessiner à l'écran.

Pour créer une surface, on passe à la méthode init les dimensions de la surface sous forme d'une tuple. Dans notre cas, ce sera les dimensions de la fenêtre.

brouillard = pygame.Surface((LARGEUR_FENETRE, HAUTEUR_FENETRE))

On la remplit ensuite d'une couleur gris uni, renseignée dans une constante.

COULEUR_BROUILLARD_MENU = (60, 60, 60)
brouillard.fill(COULEUR_BROUILLARD_MENU)

On lui donne une valeur de transparence, elle aussi renseignée dans une constante.

ALPHA_BROUILLARD_MENU = 220
brouillard.set_alpha(ALPHA_BROUILLARD_MENU)

puis on la dessine à l'écran après avoir affiché la prévisualisation, et avant d'afficher le menu.

ecran.blit(brouillard, (0,0))

Pour éviter de recréer une nouvelle surface à chaque frame, on garde le brouillard comme un attribut de la classe Menu.

class Menu:
    def __init__(self, niveaux, ecran):
        # Ces 4 booléens sont mis a True 
        # si le joueur a lancé la commande dans la frame
        self.haut = False 
        self.bas = False 
        self.lancer = False 
        self.quitter = False
        self.niveaux = niveaux
        #indice du niveau sélectionné.dans la liste des niveaux
        self.niveau_selectionne = 0 
        self.ecran = ecran

        self.textes_niveaux = {}

        fonte = pygame.font.SysFont("Mono", TAILLE_MENU, True, False)
        for n in niveaux:
            self.textes_niveaux[n.nom] = fonte.render(n.nom, True, COULEUR_MENU) 

        fonte_chrono = pygame.font.SysFont("Mono", TAILLE_CHRONOMETRE, True, False)
        self.caracteres_chrono = images_caracteres(fonte_chrono, "0123456789.", COULEUR_CHRONOMETRE)

        self.brouillard = pygame.Surface((LARGEUR_FENETRE, HAUTEUR_FENETRE))
        self.brouillard.fill(COULEUR_BROUILLARD_MENU)
        self.brouillard.set_alpha(ALPHA_BROUILLARD_MENU)

Dans la fonction d'affichage du menu, on affiche simplement le brouillard.

def afficher_menu(menu):
    ecran = menu.ecran
    ecran.fill(COULEUR_ARRIERE_PLAN)

    rendre_niveau_menu(menu)

    ecran.blit(menu.brouillard, (0,0))

    n = menu.niveau_selectionne

    rendre_nom_niveau(menu, n, 0, 255)

    # On dessine les niveaux adjacents si besoin,
    # en ajoutant un décalage de position
    if n > 1:
        rendre_nom_niveau(menu, n - 2, -ESPACEMENT_MENU*2, 255 - GAIN_TRANSPARENCE_MENU * 2)

    if n > 0:
        rendre_nom_niveau(menu, n - 1, -ESPACEMENT_MENU, 255 - GAIN_TRANSPARENCE_MENU)

    if n < len(menu.niveaux) - 1:
        rendre_nom_niveau(menu, n + 1, ESPACEMENT_MENU, 255 - GAIN_TRANSPARENCE_MENU)

    if n < len(menu.niveaux) - 2:
        rendre_nom_niveau(menu, n +2, ESPACEMENT_MENU*2, 255 - GAIN_TRANSPARENCE_MENU * 2)

    pygame.display.flip()

En lançant le jeu, on peut constater le brouillard devant le menu.

previsualisation previsualisation

Le code complet de l'étape se trouve dans le dépliant ci-dessous.

Code Complet
--8<-- "lasero/previsualisation/previsualisation.py