Aller au contenu

Affichage de fin de partie

On va reprendre le même principe que pour le chronomètre, mais cette fois-ci afficher le texte au centre de l'écran.

Images des textes

Pour créer les images des textes de victoire et de défaite, on va procéder de la même manière que pour créer les images des caractères. La méthode render de font peut rendre des textes entiers.

On crée donc les constantes dont on a besoin. On utilise la même couleur que pour le chronomètre.

TAILLE_FIN_DE_PARTIE = 150
COULEUR_FIN_DE_PARTIE = (150, 150, 170)

On génère les images directement dans la classe Rendu.

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)

Centrage d'une image à l'écran

Pour centrer l'image des textes à l'écran, on va faire un peu de géométrie.

Le centre de l'écran de largeur LARGEUR_FENETRE et de hauteur HAUTEUR_FENETRE est le point (LARGEUR_FENETRE/2, HAUTEUR_FENETRE/2).

TODO (prof) schéma ici

Si on veut tracer une image avec la méthode blit, on doit déterminer le coin haut gauche de l'image pour que l'image soit centrée.

Relativement au centre d'une image de largeur l et de hauteur h, le coin haut gauche de l'image est (-l/2, -h/2)

TODO (prof) schéma ici

Donc, si on place le centre de l'image au niveau du centre de l'écran, le coin haut gauche de l'image aura la position :

(LARGEUR_FENETRE/2 - l/2, HAUTEUR_FENETRE/2 - h/2)

TODO (prof) schéma ici

Ecrit d'une autre manière, pour un écran de centre \(c_e\) et une image de centre \(c_i\), on trouve la position \(p\) du coin avec le calcul vectoriel suivant :

\[ p = c_e - c_i \]

TODO (prof) schéma ici

Rendu

On peut maintenant écrire une fonction rendre_image_centree. La méthode get_height() permet d'obtenir la hauteur d'une image.

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)

On peut utiliser cette fonction pour afficher la fin de partie quand c'est nécessaire.

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)

On appelle cette nouvelle fonction dans la fonction de rendu, avant de dessiner les projectiles et le personnage.

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

    rendre_chronometre(etat)

    rendre_fin_de_partie(etat)

    rendre_projectiles(etat)

    rendre_personnage(etat)

    pygame.display.flip()  

Boucle principale

Dans la boucle principale, on retire les prints relatifs à l'affichage de la fin de partie.

while not etat.controles.quitter:

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

    t = clock.tick(FPS)/1000

    traiter_controles(etat, t)

    if not etat.simulation.collision and etat.simulation.decompte_victoire != 0.0:
        avancer_simulation(etat, t)

    afficher_rendu(etat, t)

Si on lance le jeu, on pourra voir la fin de partie correspondante.

victoire

defaite

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

Code Complet
import pygame
from pygame.math import Vector2

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

## Général 

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
TAILLE_PROJECTILES = 10
VITESSE_PROJECTILES = 200
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)

####################
####  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

class Projectile:

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

class Simulation:

    def __init__(self, personnage):
        self.personnage = personnage
        self.projectiles = [
            Projectile(Vector2(200, 300), Vector2(1.0, 1.0)),
            Projectile(Vector2(400, 120), Vector2(1.0, 0)),
            Projectile(Vector2(800, 600), Vector2(-1.1, -0.3)),
            Projectile(Vector2(200, 600), Vector2(0.2, -0.1)),
        ]
        self.collision = False
        self.decompte_victoire = DECOMPTE_VICTOIRE


def initialiser_simulation(etat):
    pos = Vector2(LARGEUR_FENETRE/2, HAUTEUR_FENETRE/2)
    p = Personnage(pos)
    etat.simulation = Simulation(p)

def reinitialiser_simulation(etat):
    initialiser_simulation(etat)

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, TAILLE_PROJECTILES):
            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 * VITESSE_PROJECTILES * t

def faire_rebondir_projectiles(etat):
    for p in etat.simulation.projectiles:
        if p.position.x < TAILLE_PROJECTILES:
            p.position.x = TAILLE_PROJECTILES
            p.direction.x *= -1
        if p.position.x > LARGEUR_FENETRE - TAILLE_PROJECTILES:
            p.position.x = LARGEUR_FENETRE - TAILLE_PROJECTILES
            p.direction.x *= -1
        if p.position.y < TAILLE_PROJECTILES:
            p.position.y = TAILLE_PROJECTILES
            p.direction.y *= -1
        if p.position.y > HAUTEUR_FENETRE - TAILLE_PROJECTILES:
            p.position.y = HAUTEUR_FENETRE - TAILLE_PROJECTILES
            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 avancer_simulation(etat, t):
    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(etat):
    ecran = etat.rendu.ecran
    position = etat.simulation.personnage.position
    pygame.draw.circle(ecran, COULEUR_PERSONNAGE, ref_sve(position), TAILLE_PERSONNAGE)

def rendre_projectiles(etat):
    ecran = etat.rendu.ecran
    for p in etat.simulation.projectiles:
        position = p.position
        pygame.draw.circle(ecran, COULEUR_PROJECTILES, ref_sve(position), TAILLE_PROJECTILES)

def rendre_chronometre(etat):
    chrono = f"{etat.simulation.decompte_victoire:06.2f}" #Conversion en chaine
    caracteres = etat.rendu.caracteres_chrono

    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:
        etat.rendu.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)

    rendre_fin_de_partie(etat)

    rendre_projectiles(etat)

    rendre_personnage(etat)

    pygame.display.flip()  

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

def main():
    pygame.init()

    etat = GameState()

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

    initialiser_controles(etat)
    initialiser_simulation(etat)
    initialiser_rendu(etat, fenetre)

    clock = pygame.time.Clock()

    while not etat.controles.quitter:

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

        t = clock.tick(FPS)/1000

        traiter_controles(etat, t)

        if not etat.simulation.collision and etat.simulation.decompte_victoire != 0.0:
            avancer_simulation(etat, t)

        afficher_rendu(etat, t)

    pygame.quit()

if __name__ == "__main__":
    main()