Vous pouvez me contacter via Facebook pour questions & suggestions :
Page Facebook relative à mon site
Concernant les Rect tout n'a pas été dit.
Des astuces programmatiques très utiles n'ont pas encore été évoquées.
Jusqu'à présent pour savoir si un sprite était ou non sorti de la fenêtre de jeu il fallait quatre tests : un pour
chaque bord.
Je vous propose ci-dessous un technique très simple (un seul test). Il faut cependant créer au préalable un objet de type
rect correspondant à la fenêtre de jeu.
Autre nouveauté dans ce script : emploi de la méthode move_ip() à la place de move().
import pygame
from pygame.locals import *
pygame.init()
X, Y = 640, 480
fenetre = pygame.display.set_mode((X, Y))
# création d'un objet rect correspondant à la fenêtre de jeu
fenetre_rect = pygame.Rect(0, 0, X, Y)
# sprite
surface = pygame.Surface((50, 50))
surface.fill((255, 0, 0))
sprite = surface.get_rect(topleft=(X/2, Y/2))
# conversion surface en objet rect nommé 'sprite'
clock = pygame.time.Clock()
points = 20
boucler = True
#-------------boucle de jeu---------------
while boucler :
for event in pygame.event.get():
if event.type == QUIT:
running = False
# déplacement du sprite
keys = pygame.key.get_pressed()
if keys[K_RIGHT]:
sprite.move_ip(5, 0)
if keys[K_LEFT]:
sprite.move_ip(-5, 0)
if keys[K_UP]:
sprite.move_ip(0, -5)
if keys[K_DOWN]:
sprite.move_ip(-5, 5)
# vérification de sortie du sprite de la fenêtre
if not fenetre_rect.contains(sprite):
print("Le sprite est hors de l’écran !")
points -=1
if points < 0 :
boucler = False
fenetre.fill((0, 0, 0))
fenetre.blit(surface, sprite)
pygame.display.flip()
clock.tick(10)
# ----------------------
pygame.quit()
print("Vous avez perdu")
Si le sprite reste en dehors de la fenêtre durant 20 itérations cumulées, il y a sortie de la boucle de jeu.
fenetre_rect = pygame.Rect(0, 0, X, Y) : création d'un objet rect nommé "fenetre_rect" correspondant
à la fenêtre de jeu.
sprite = surface.get_rect(topleft=(X/2, Y/2)) : le sprite est un Rect.
if not fenetre_rect.contains(sprite): La méthode objet1.contains(objet2) vérifie que l'objet2 est entièrement
contenu dans l'objet1 ; la méthode retourne False si ce n'est pas le cas.
sprite.move_ip(5, 0) : La méthode move_ip () déplace directement de 5px vers la droite le Rect nommé "sprite".
# pygame_echelle.py
import pygame
pygame.init()
X = 600
Y = 600
fenetre = pygame.display.set_mode((X,Y))
pygame.display.set_caption('transformations')
#incorporation d'une image
image_originale = pygame.image.load("images_sons/lion.png").convert_alpha()
# création d'un objet rect à partir de l'image
origine_rect = image_originale.get_rect()
centre_fenetre = (X//2,Y//2)
clock = pygame.time.Clock()
echelle_facteur = 1.0
echelle_pas = 0.05
boucle_jeu = True
#-----------------
while boucle_jeu :
for event in pygame.event.get():
if event.type ==pygame.QUIT:
boucle_jeu =False
echelle_facteur += echelle_pas
nouvelleL = int(origine_rect.width * echelle_facteur)
nouvelleH = int(origine_rect.height * echelle_facteur)
#nouvelle surface
image_agrandie = pygame.transform.scale(image_originale,(nouvelleL,nouvelleH))
# création d'un objet rect à partir d'une surface
agrandie_rect = image_agrandie.get_rect()
agrandie_rect.center = centre_fenetre
fenetre.fill('navy')
fenetre.blit(image_agrandie,agrandie_rect)
pygame.display.flip()
clock.tick(8)
#---------------
pygame.quit()
origine_rect = image_originale.get_rect() : création d'un objet rect à partir de l'image.
echelle_facteur += echelle_pas : la valeur de "echelle_facteur" augmente de 0.05
nouvelleL = int(origine_rect.width * echelle_facteur) : nouvelle largeur de l'image
image_agrandie = pygame.transform.scale(image_originale,(nouvelleL,nouvelleH)) : nouvelle image à partir de l'originale.
agrandie_rect = image_agrandie.get_rect() : création d'un objet rect correspondant à la nouvelle image
agrandie_rect.center = centre_fenetre : l'image et son rect associé sont centrés dans la fenêtre de jeu
fenetre.blit(image_agrandie,agrandie_rect) : dessiner l'image et son Rect associé
Problème : l'image PNG n'a pas un fond transparent mais un fond blanc ...
Heureusement il y a une solution ; voir le script suivant.
Dans l'exemple ci-dessous deux individus peuvent joueur et déplacer chacun un sprite.
# pygame_images_complements.py
import pygame
import random
import time
pygame.init()
tuile_dim =30
tuile_nombre = 20
largeur = tuile_dim * tuile_nombre
hauteur = tuile_dim * tuile_nombre
fenetre = pygame.display.set_mode((largeur,hauteur))
pygame.display.set_caption("compléments sur les images")
frequence = pygame.time.Clock()
class Predateurs(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.image.load("images_sons/lion.png").convert_alpha()
self.image =pygame.transform.scale(self.image,(tuile_dim*2,tuile_dim *2))
self.image.set_colorkey('white')
self.rect = self.image.get_rect()
# création d'un groupe de sprites
tous = pygame.sprite.Group()
# deux instance de la classe Predateurs
lion = Predateurs()
lion.rect.bottomright = (largeur,hauteur)
lionne =Predateurs()
lionne.rect.topleft = (0,0)
tous.add(lion)
tous.add(lionne)
encore = True
# --------------boucle de jeu------------------
while encore :
for event in pygame.event.get():
if event.type == pygame.QUIT:
encore = False
# deplacement par clavier du lion qui ne peut sortir de la fenêtre de jeu
touches = pygame.key.get_pressed()
if lion.rect.left >=0 :
if touches[pygame.K_LEFT]:
lion.rect.x -= 10
if lion.rect.right <= largeur :
if touches[pygame.K_RIGHT]:
lion.rect.x += 10
if lion.rect.top >= 0 :
if touches[pygame.K_UP]:
lion.rect.y -= 10
if lion.rect.bottom <= hauteur :
if touches[pygame.K_DOWN]:
lion.rect.y += 10
# deplacement par clavier de la lionne qui peut sortir de la fenêtre de jeu
touches = pygame.key.get_pressed()
if touches[pygame.K_d]:
lionne.rect.x -= 10
if touches[pygame.K_q]:
lionne.rect.x += 10
if touches[pygame.K_z]:
lionne.rect.y -= 10
if touches[pygame.K_w]:
lionne.rect.y += 10
fenetre.fill('wheat')
# affichage des sprites
tous.draw(fenetre)
# actualisation
pygame.display.flip()
frequence.tick(8)
# -----------------
pygame.quit()
Notez la technique des "tuiles" pour dimensionner la fenêtre de jeu.
Modifiez les variables "tuile_dim" et "tuile_nombre" pour redimensionner le jeu sans devoir modifier d'autres instructions
du script.
self.image =pygame.transform.scale(self.image,(tuile_dim*2,tuile_dim *2)) : les dimensions de l'image sont modifiées ;
l'image fera 2 tuiles sur donc 60 par 60 (tant que "tuile_dim" vaut 30).
self.image.set_colorkey('white') : la méthode set_colorkey(couleur) permet de rendre une couleur spécifique transparente.
Donc ici je demande que le fond blanc devienne transparent. J'aurai pu écrire la couleur blanche en notation RGV sous forme d'un tuple : (255,255,255).
self.rect = self.image.get_rect() : création d'un Rect à partir de l'image.
if touches[pygame.K_d]: : si appui sur la touche "D" du clavier.
lion.rect.bottomright = (largeur,hauteur) : L'instance de Prédateur nommée "lion" est positionnée dans le coin bas droit.
lionne.rect.topleft = (0,0) : l'instance de Prédateur nommée "lionne" est positionnée dans le coin haut gauche.
Les images ont désormais un fond transparent et elles ont été redimensionnées.
Vous savez déplacer un sprite via les touches du clavier mais quid du déplacement via la souris ?
Avant de déplacer le Rect via la souris, il faut faire un clic gauche sur l'objet.
Vous pouvez alors effectuer un glissement ('drag') du Rect.
Dès que vous avez cessé l'appui d'appuyer sur le bouton de la souris, le 'drag' n'est plus possible.
# pygame_deplacer_rect_avec_souris.py
import pygame
pygame.init()
X = 600
Y = 600
fenetre = pygame.display.set_mode((X, Y))
clock = pygame.time.Clock()
# Rect à déplacer nommé 'sprite'
sprite = pygame.Rect(X/2, Y/2,50,50)
glisser = False # indique si le rect est en cours de glissement
boucler = True
#----------boucle de jeu--------------
while boucler :
for evenement in pygame.event.get():
if evenement.type == pygame.QUIT:
boucler = False
# Clic de la souris
if evenement.type == pygame.MOUSEBUTTONDOWN:
if evenement.button == 1: # bouton gauche de la souris
if sprite.collidepoint(evenement.pos): # le clic est sur le Rect
glisser = True
# Relâchement du bouton
if evenement.type == pygame.MOUSEBUTTONUP:
if evenement.button == 1:
glisser = False
# Mouvement de la souris
if evenement.type == pygame.MOUSEMOTION:
if glisser :
souris_x, souris_y = pygame.mouse.get_pos()
sprite.x = souris_x
sprite.y = souris_y
print(f"position souris : {pygame.mouse.get_pos()}")
fenetre.fill('aqua')
pygame.draw.rect(fenetre,'navy',sprite)
pygame.display.flip()
clock.tick(10)
# --------------------------
pygame.quit()
Nouveauté dans ce programme : méthode Rect.collidepoint(coordonnées XY) qui retourne True si les coordonnées appartiennent au Rect.
Je vous invite à revoir éventuellement le chapitre 27.
if evenement.type == pygame.MOUSEBUTTONDOWN : si appui sur un bouton de la souris.
if evenement.button == 1 : si appui sur le bouton gauche de la souris.
evenement.pos : coordonnées de la souris sous forme d'un tuple.
if sprite.collidepoint(evenement.pos) : si la souris pointe bien le Rect.
if evenement.type == pygame.MOUSEBUTTONUP: si relachement de l'appui
if evenement.type == pygame.MOUSEMOTION: si déplacement de la souris
souris_x, souris_y = pygame.mouse.get_pos() : affectation des coordonnées xy de la souris à deux variables.
sprite.x = souris_x : nouvelle coordonnée x du Rect
Je vous invite à recréer ce script.
Vous verrez qu'il y a un petit inconvénient : un décalage ('offset') entre la position de la souris et le coin haut gauche du Rect.
...
# Mouvement de la souris
if evenement.type == pygame.MOUSEMOTION:
if glisser :
souris_x, souris_y = pygame.mouse.get_pos()
sprite.centerx = souris_x
sprite.centery = souris_y
print(f"position souris : {pygame.mouse.get_pos()}")
Il faut mieux utiliser les propriétés centerx & centery pour éviter "l'offset" constaté dans la première version.
Je vous propose un jeu en guise de synthèse ; une version toute personnelle du fameux jeu du serpent. Le serpent doit dévorer 30 souris dans le minimum de temps.
# nom du programme : jungle_cruelle.py
# apparition aléatoire de souris que le serpent doit dévorer avant qu'elles ne disparaissent
import pygame, random
pygame.init()
pygame.mixer.init()
tuile = 25
tuile_nombre = 25
delta = tuile
compteur = 0
style= pygame.font.SysFont("Arial", 30, 0, 0)
clock = pygame.time.Clock()
son1 = pygame.mixer.Sound("images_sons/crash1.mp3")
son2 = pygame.mixer.Sound("images_sons/crash2.mp3")
X =tuile * tuile_nombre
Y = tuile * tuile_nombre
duree_vie = 2000 #durée d'apparition d'une souris
fenetre = pygame.display.set_mode((X,Y))
pygame.display.set_caption('Le jeu du serpent')
# création d'un Rect correspondant à la fenêtre de jeu
fenetre_rect = pygame.Rect(0, 0, X, Y)
# déclaration des classes
class Serpents(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.transform.scale(pygame.image.load('images_sons/serpent.png') /
.convert_alpha(),(tuile*2,tuile*2))
self.rect = self.image.get_rect()
class Souris(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.transform.scale(pygame.image.load('images_sons/souris.png') /
.convert_alpha(), (tuile, tuile))
self.rect = self.image.get_rect()
# génération alétoire des coordonnées d'une nouvelle souris
self.rect.right = random.randint(0, X)
self.rect.top = random.randint(0, Y)
self.instant = pygame.time.get_ticks()
def update(self):
#suppresion des vieilles souris
if pygame.time.get_ticks() - self.instant > duree_vie :
self.kill()
# ----------------------
# groupes de sprites :
liste_souris = pygame.sprite.Group()
liste_sprites = pygame.sprite.Group()
# une instance de serpents :
serpent = Serpents()
serpent.rect.center =(X //2, Y//2)
liste_sprites.add(serpent)
debut_jeu = pygame.time.get_ticks()
fin_jeu =0
#---------fonctions
def ecrire_regles():
regles = [
"Règle 1 : Utilisez la souris pour déplacer le serpent.",
"Règle 2 : Évitez de sortir de la fenêtre de jeu.",
"Règle 3 : Tuer 30 souris le plus vite possible.",
"Appuyez sur ESPACE pour lancer le jeu ! "
]
y = Y/2
fenetre.fill('ivory')
for ligne in regles:
texte_surface = style.render(ligne, True,'navy')
texte_rect = texte_surface.get_rect(center=(X/2, y))
fenetre.blit(texte_surface, texte_rect)
y += 40
pygame.display.flip()
def score():
fin_jeu = pygame.time.get_ticks()
temps_jeu = (fin_jeu - debut_jeu) //1000
message = f"Vous avez gagné en {temps_jeu} secondes"
texte_surface = style.render(message, 1, 'navy')
texte_rect = texte_surface.get_rect(center=(X/2, Y/2))
fenetre.fill('ivory')
fin = True
fenetre.blit(texte_surface, texte_rect)
pygame.display.flip()
pygame.time.wait(5000)
pygame.quit()
#------variables pour contrôler les boucles--------
affichage = True
fin = False
glisser = False
# -------------boucle d'affichage des règles-------
while affichage :
for event in pygame.event.get():
if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
affichage = False
ecrire_regles()
clock.tick(16)
#------------boucle de jeu--------------
while not fin:
# création éventuelle d'un objet Souris à chaque itération de la boucle de jeu
alea = random.randint(1,4)
if alea == 1 :
souris = Souris()#création d'une instance de Souris
liste_souris.add(souris)
liste_sprites.add(souris)
liste_souris.update() #appel de la méthode qui supprime les vieilles souris
# deplacement du serpent par la souris, serpent peut sortir de la fenêtre de jeu
for evenement in pygame.event.get():
if evenement.type == pygame.QUIT:
fin = True
# Clic de la souris
if evenement.type == pygame.MOUSEBUTTONDOWN:
if evenement.button == 1: # bouton gauche de la souris
if serpent.rect.collidepoint(evenement.pos): # le clic est sur le Rect
glisser = True
# Relâchement du bouton
if evenement.type == pygame.MOUSEBUTTONUP:
if evenement.button == 1:
glisser = False
# Mouvement de la souris
if evenement.type == pygame.MOUSEMOTION:
if glisser :
sourisX, sourisY = pygame.mouse.get_pos()
serpent.rect.center = (sourisX,sourisY)
# gestion collision
if pygame.sprite.spritecollide(serpent,liste_souris,True):
compteur +=1
son2.play()
# pénalisation si serpent sort de la fenêtre de jeu
if not fenetre_rect.contains(serpent):
son1.play()
compteur -=1
# si objectif atteint
if compteur ==30 :
score() #appel fonction qui affiche le score
# dans tous les cas :
fenetre.fill("ivory")
clock.tick(16)
liste_sprites.draw(fenetre)
pygame.display.flip()
Dans la définition de la classe Souris() vous trouvez le code suivant :
# génération alétoire des coordonnées d'une nouvelle souris
self.rect.right = random.randint(0, X)
self.rect.top = random.randint(0, Y)
self.instant = pygame.time.get_ticks()
def update(self):
#suppresion des vieilles souris
if pygame.time.get_ticks() - self.instant > duree_vie :
self.kill()
Le positionnement d'une souris est aléatoire.
Et dans la boucle de jeu on retrouve le bloc ci-dessous :
# ajout & suppression de souris
alea = random.randint(1,4)
if alea == 1 :
souris = Souris() #création d'une instance de Souris
liste_souris.add(souris)
liste_sprites.add(souris)
liste_souris.update() #appel de la méthode qui supprime les vieilles souris
Une nouvelle souris est créée uniquement si alea vaut 1 ; son positionnement est généré de façon aléatoire.
Toujours dans la boucle de jeu appel de la méthode update(self) qui supprime les vieilles souris : self.kill().
La méthode kill() supprime le sprite de tous les groupes (à la différence de remove()).
Observez attentivement la fonction ecrire_regles() qui affiche sur N lignes les règles du jeu.
Notez l'emploi d'une liste et d'une boucle pour afficher chaque élément de la liste.
La boucle d'affichage des règles :
while affichage :
for event in pygame.event.get():
if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
affichage = False
ecrire_regles() #appel fonction
clock.tick(16)
Les règles du jeu sont affichées (appel fonction ecrire_regles()) tant que le joueur n'a pas appuyé sur la touche ESPACE pour entre alors dans la boucle de jeu.
Un seul test !
if not fenetre_rect.contains(serpent):
son1.play()
compteur -=1
fenetre_rect est un Rect qui correspond à la fenêtre de jeu ; le compteur est décrémenté.
if pygame.sprite.spritecollide(serpent,liste_souris,True):
compteur +=1
son2.play()
L'argument "dokill" est à True.
Fonction appelée dès que "compteur" atteint 30.
def score() :
fin_jeu = pygame.time.get_ticks()
temps_jeu = (fin_jeu - debut_jeu) //1000
message = f"Vous avez gagné en {temps_jeu} secondes"
texte_surface = style.render(message, 1, 'navy')
texte_rect = texte_surface.get_rect(center=(X/2, Y/2))
fenetre.fill('ivory')
fin = True
fenetre.blit(texte_surface, texte_rect)
pygame.display.flip()
pygame.time.wait(5000)
pygame.quit()
Le shell n'est pas utilisé ; tout est affiché dans la fenêtre de jeu.
Les règles du jeu :
Le jeu :
Téléchargez le programme
Afin que les chemins relatifs vers les images et les fichiers audio soient corrects, transférez le script dans le dossier
"c:/pygame_jeux" (qui contient le répertoire "images_sons").
Dans cette nouvelle version des choses vertes (des salades) apparaissent sur le parcours.
Il existe une astuce programmatique pour que ces objets soient positionnés dans les couloirs (et pas dans les parois).
Le chronomètre s'arrête dès que le personnage atteint l'aire d'arrivée et à condition d'avoir avalé toutes les 'salades'.
Par ailleurs j'ai sonorisé le jeu.
Je ne vous présente que les ajouts / corrections par rapport à la version initiale.
# variables globales tuile = 30 nbre_tuiles = 25 maxi = nbre_tuiles-2 largeur_laby = tuile * nbre_tuiles hauteur_laby = tuile * nbre_tuiles X = largeur_laby +20 Y = hauteur_laby + 20
Donc si vous utilisez comme trame des fichiers TXT de 40 par 40 caractères, il suffira de changer la valeur de "nbre_tuiles".
pygame.init()
style= pygame.font.SysFont("Arial", 30, 0, 0)
pygame.mixer.init()
son1 = pygame.mixer.Sound("images_sons/crash1.mp3")
son2 = pygame.mixer.Sound("images_sons/crash2.mp3")
Il faut définir une nouvelle classe.
class Choses(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((28,28))
self.image.fill('green')
self.rect = self.image.get_rect()
self.rect.x = random.randint(2,maxi)*tuile
self.rect.y = random.randint(2,maxi)* tuile
Le positionnement d'une instance de Choses est généré de façon aléatoire. Ça serait mieux d'afficher une image de salade plutôt qu'un carré vert ...
mur = pygame.sprite.Group() tous = pygame.sprite.Group() choses = pygame.sprite.Group()
Il y a maintenant trois groupes de sprites.
Étudiez très attentivement la boucle ci-dessous.
Les "salades" doivent être créées dans les couloirs (et pas ailleurs).
Donc il faut créer une instance de Choses s'il n'y a pas de conflit entre 'mur' et cette chose.
while len(choses) < 10 :
chose = Choses()
conflits = pygame.sprite.spritecollide(chose,mur,False)
# création d'une chose uniquement si celle-ci n'est pas localisée dans le mur
if len(conflits) ==0 :
choses.add(chose)
tous.add(chose)
Si la liste "conflits" est vide la "chose" est bien créée.
La boucle est effectuée tant que la longueur du groupe 'choses' est inférieur à 10.
Il faut rajouter dans la boucle de jeu les collisions entre personnage et choses.
if pygame.sprite.spritecollide(personnage,choses,True):
son1.play()
L'argument "dokill" est bien sûr à True puisque le personnage doit cueillir toutes les salades.
if (personnage.rect.y >= largeur_laby) and (personnage.rect.y >= largeur_laby) and len(choses)==0 :
score() # appel fonction qui affiche le score
Trois conditions doivent être remplies pour qu'il y ait fin de partie.
def score():
fin_jeu = pygame.time.get_ticks()
temps_jeu = (fin_jeu - debut_jeu) //1000
temps_jeu = int(temps_jeu)
message = f"Vous avez gagné en {temps_jeu} secondes"
texte_surface = style.render(message, 1, 'navy')
texte_rect = texte_surface.get_rect(center=(X/2, Y/2))
encore = False
fenetre.fill('white')
fenetre.blit(texte_surface, texte_rect)
pygame.display.flip()
pygame.time.wait(5000)
pygame.quit()
Le score (temps parcouru) est affiché dans la fenêtre de jeu (et plus dans le shell).
Observez bien les dix choses ('salades' vertes) placées dans les couloirs.
Téléchargez le fichier .zip
Après décompression vous obtiendrez le fichier.py et les trois fichiers .txt.
Déplacez tous ces fichiers vers le dossier "c:\pygame_jeux" (qui contient déjà le sous-dossier "images_sons").
Enfin, vous pourrez jouer !
On pourrait imaginer que le joueur puisse faire des choix tactiques ; ne pas avaler une 'salade' trop éloignée du parcours optimal.
Dans ce cas il est pénalisé d'une seconde par 'salade' non avalée mais ça peut valoir le coup ...
On pourrait imaginer aussi qu'il soit pénalisé d'une seconde pour chaque collision avec les cloisons.
Seriez-vous capable d'imaginer une nouvelle version de la fonction score() avec ces différents malus ?