Aller au contenu

Paramètrage du niveau

Dans cette étape, on prépare les étapes suivantes, qui vont consister à laisser le choix du niveau au joueur par le biais d'un menu.

Pour commencer, on doit rendre la partie "jeu" à proprement parler (la boucle principale) un peu plus flexible.

L'idée générale est de créer une fonction jouer_niveau, qui prends en paramètre les données pour créer l'état initial de la simulation :

  • Position initiale du personnage
  • Temps à survivre pour la gagner
  • Description des projectiles (position, direction)

Copie des données

On va réutiliser les différentes classes de simulation. Toutefois, on veut éviter la modification des données dans le niveau, pour pouvoir les réutiliser en cas de réinitialisation du niveau.

On définit donc des fonctions de copie pour les classes de simulation que l'on veut utiliser pour décrire un niveau. En particulier, il faut penser à copier les Vector2 avec leur méthode copy

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

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

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

Classe Niveau et Simulation

On définit une classe Niveau, qui contient les données nécessaires au niveau.

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

On modifie la classe Simulation de manière à ce qu'elle prenne un niveau comme seul paramètre de sa méthode __init__. On copie les données du niveau.

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

La fonction initialiser_simulation prends également le niveau en paramètre.

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

Tout comme la fonction reinitialiser_simulation.

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

Fonction main

Il nous faut modifier la fonction main en conséquence. On doit, entre autres, y créer un niveau et le passer à la simulation.

def main():
    pygame.init()

    etat = GameState()

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

    niveau = Niveau(
        Personnage(Vector2(LARGEUR_FENETRE/2, HAUTEUR_FENETRE/2)),
        DECOMPTE_VICTOIRE,
        [   
            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)),
        ]
        )

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

    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)

    pygame.quit()

Notre jeu est prêt à être lancé. Quand on le lance, on remarque que le jeu se comporte exactement comme avant, et pourtant le code a changé.

Nous avons fait ici ce que l'on appelle un refactoring, que l'on pourrait traduire en français par remaniement. C'est une étape qui consiste à rendre le code d'un logiciel meilleur sans changer le comportement du logiciel.

Avec le temps et des modifications successives, il est courant que la qualité du code se dégrade. Il devient moins lisible, ou moins facile à modifier. C'est pourquoi il est conseillé de faire du refactoring de code régulièrement.

Le code complet de l'étape à ce point ce 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

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

class Projectile:

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

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

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

class Niveau:
    def __init__(self, personnage, decompte_victoire, projectiles):
        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, 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 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(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])

    niveau = Niveau(
        Personnage(Vector2(LARGEUR_FENETRE/2, HAUTEUR_FENETRE/2)),
        DECOMPTE_VICTOIRE,
        [   
            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)),
        ]
        )

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

    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)

    pygame.quit()

if __name__ == "__main__":
    main()

Fonction de niveau

Continuons notre refactoring, pour finalement écrire la fonction jouer_niveau, contenant tous les éléments de la boucle principale qui permettent de jouer un niveau. Elle prends le niveau à jouer en paramètre, ainsi que l'écran. On va la placer dans une nouvelle section, "Noyau", qui va contenir toutes les fonction qui concernent le jeu en général, et pas seulement un des systèmes (controles, simulation, rendu).

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

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)
Et voilà, on a simplement fait un copier coller et modifié fenetre en ecran.

Il ne nous reste qu'à modifier la fonction main pour qu'elle appelle cette fonction.

def main():
    pygame.init()

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

    niveau = Niveau(
        Personnage(Vector2(LARGEUR_FENETRE/2, HAUTEUR_FENETRE/2)),
        DECOMPTE_VICTOIRE,
        [   
            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)),
        ]
        )

    jouer_niveau(niveau, ecran)

    pygame.quit()

Voilà, le comportement de notre programme n'a pas changé, mais le code est prêt pour des modifications plus poussées !

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

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

class Projectile:

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

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

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

class Niveau:
    def __init__(self, personnage, decompte_victoire, projectiles):
        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, 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 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(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()  


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

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)

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

def main():
    pygame.init()

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

    niveau = Niveau(
        Personnage(Vector2(LARGEUR_FENETRE/2, HAUTEUR_FENETRE/2)),
        DECOMPTE_VICTOIRE,
        [   
            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)),
        ]
        )

    jouer_niveau(niveau, fenetre)

    pygame.quit()

if __name__ == "__main__":
    main()

TODO (prof) continuer ici