TP : balles rebondissantes
1. Prise en main de Pygame
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 | import pygame, sys
import time
from pygame.locals import *
LARGEUR = 640
HAUTEUR = 480
RAYON = 20
pygame.display.init()
fenetre = pygame.display.set_mode((LARGEUR, HAUTEUR))
fenetre.fill([0,0,0])
x = 300
y = 200
dx = 4
dy = -3
couleur = (45, 170, 250)
while True:
fenetre.fill([0, 0, 0])
pygame.draw.circle(fenetre, couleur, (x, y), RAYON)
x += dx
y += dy
pygame.display.update()
# routine pour pouvoir fermer «proprement» la fenêtre Pygame
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.display.quit()
sys.exit()
time.sleep(0.1)
|
1.1 Rajout d'un rebond sur les parois
Modifiez le code précédent afin que la balle rebondisse sur chaque paroi (il suffit de modifier intelligemment les variables de vitesse dx
et dy
).
Correction
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 | import pygame, sys
import time
from pygame.locals import *
LARGEUR = 640
HAUTEUR = 480
RAYON = 20
pygame.display.init()
fenetre = pygame.display.set_mode((LARGEUR, HAUTEUR))
fenetre.fill([0, 0, 0])
x = 300
y = 200
dx = 4
dy = -3
couleur = (45, 170, 250)
while True:
fenetre.fill([0, 0, 0])
pygame.draw.circle(fenetre, couleur, (x, y), RAYON)
x += dx
y += dy
if (y <= RAYON) or (y >= HAUTEUR - RAYON):
dy = -dy
if (x <= RAYON) or (x >= LARGEUR - RAYON):
dx = -dx
pygame.display.update()
# routine pour pouvoir fermer «proprement» la fenêtre Pygame
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.display.quit()
sys.exit()
time.sleep(0.02)
|
1.2 Rajout d'une deuxième balle
Attention au nommage des variables...
Correction
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64 | import pygame, sys
import time
from pygame.locals import *
LARGEUR = 640
HAUTEUR = 480
RAYON = 20
pygame.display.init()
fenetre = pygame.display.set_mode((LARGEUR, HAUTEUR))
fenetre.fill([0, 0, 0])
dxA = 7
dyA = 4
dxB = -5
dyB = 3
xA = LARGEUR // 3
yA = HAUTEUR // 2
xB = LARGEUR // 2
yB = HAUTEUR // 2
couleurA = (45, 170, 250)
couleurB = (155, 17, 250)
while True:
fenetre.fill([0, 0, 0])
pygame.draw.circle(fenetre, couleurA, (xA, yA), RAYON)
pygame.draw.circle(fenetre, couleurB, (xB, yB), RAYON)
xA += dxA
yA += dyA
xB += dxB
yB += dyB
# rebond en haut ou en bas
if (yA < RAYON) or (yA > HAUTEUR - RAYON):
dyA = -dyA
# rebond à gauche ou à droite
if (xA < RAYON) or (xA > LARGEUR - RAYON):
dxA = -dxA
# rebond en haut ou en bas
if (yB < RAYON) or (yB > HAUTEUR - RAYON):
dyB = -dyB
# rebond à gauche ou à droite
if (xB < RAYON) or (xB > LARGEUR - RAYON):
dxB = -dxB
pygame.display.update()
# routine pour pouvoir fermer «proprement» la fenêtre Pygame
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.display.quit()
sys.exit()
time.sleep(0.03)
|
1.3 Gestion de la collision entre les deux balles
Q1. À l'aide d'un schéma (papier-crayon !), mettez en évidence le test devant être réalisé pour détecter une collision.
indice
Q2. Implémentez ce test (en créant pour cela une fonction distance
) et affichez "collision" en console lorsque les deux balles se touchent.
Correction
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72 | import pygame, sys
import time
from pygame.locals import *
LARGEUR = 640
HAUTEUR = 480
RAYON = 20
pygame.display.init()
fenetre = pygame.display.set_mode((LARGEUR, HAUTEUR))
fenetre.fill([0, 0, 0])
dxA = 7
dyA = 4
dxB = -5
dyB = 3
xA = LARGEUR // 3
yA = HAUTEUR // 2
xB = LARGEUR // 2
yB = HAUTEUR // 2
couleurA = (45, 170, 250)
couleurB = (155, 17, 250)
def distanceAB(xA, yA, xB, yB):
return ((xA-xB)**2 + (yA-yB)**2)**0.5
while True:
fenetre.fill([0, 0, 0])
pygame.draw.circle(fenetre, couleurA, (xA, yA), RAYON)
pygame.draw.circle(fenetre, couleurB, (xB, yB), RAYON)
xA += dxA
yA += dyA
xB += dxB
yB += dyB
# rebond en haut ou en bas
if (yA < RAYON) or (yA > HAUTEUR - RAYON):
dyA = -dyA
# rebond à gauche ou à droite
if (xA < RAYON) or (xA > LARGEUR - RAYON):
dxA = -dxA
# rebond en haut ou en bas
if (yB < RAYON) or (yB > HAUTEUR - RAYON):
dyB = -dyB
# rebond à gauche ou à droite
if (xB < RAYON) or (xB > LARGEUR - RAYON):
dxB = -dxB
if distanceAB(xA, yA, xB, yB) < 2 * RAYON:
print('collision')
pygame.display.update()
# routine pour pouvoir fermer «proprement» la fenêtre Pygame
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.display.quit()
sys.exit()
time.sleep(0.03)
|
Q3. Pour donner l'illusion physique du rebond, échangez les valeurs respectives de dx
et dy
pour les deux balles.
Correction
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73 | import pygame, sys
import time
from pygame.locals import *
LARGEUR = 640
HAUTEUR = 480
RAYON = 20
pygame.display.init()
fenetre = pygame.display.set_mode((LARGEUR, HAUTEUR))
fenetre.fill([0, 0, 0])
dxA = 7
dyA = 4
dxB = -5
dyB = 3
xA = LARGEUR // 3
yA = HAUTEUR // 2
xB = LARGEUR // 2
yB = HAUTEUR // 2
couleurA = (45, 170, 250)
couleurB = (155, 17, 250)
def distanceAB(xA, yA, xB, yB):
return ((xA-xB)**2 + (yA-yB)**2)**0.5
while True:
fenetre.fill([0, 0, 0])
pygame.draw.circle(fenetre, couleurA, (xA, yA), RAYON)
pygame.draw.circle(fenetre, couleurB, (xB, yB), RAYON)
xA += dxA
yA += dyA
xB += dxB
yB += dyB
# rebond en haut ou en bas
if (yA < RAYON) or (yA > HAUTEUR - RAYON):
dyA = -dyA
# rebond à gauche ou à droite
if (xA < RAYON) or (xA > LARGEUR - RAYON):
dxA = -dxA
# rebond en haut ou en bas
if (yB < RAYON) or (yB > HAUTEUR - RAYON):
dyB = -dyB
# rebond à gauche ou à droite
if (xB < RAYON) or (xB > LARGEUR - RAYON):
dxB = -dxB
if distanceAB(xA, yA, xB, yB) < 2 * RAYON:
dxA, dxB = dxB, dxA
dyA, dyB = dyB, dyA
pygame.display.update()
# routine pour pouvoir fermer «proprement» la fenêtre Pygame
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.display.quit()
sys.exit()
time.sleep(0.03)
|
1.4 Rajout d'une troisième balle et gestion du rebond avec les deux autres.
... vraiment ? Peut-on continuer comme précédemment ?
2. La POO à la rescousse : création d'une classe Balle
2.1 la classe Balle
L'objectif est que la méthode constructeur dote chaque nouvelle balle de valeurs aléatoires : abscisse, ordonnée, vitesse, couleur...
-
Pour l'aléatoire, on pourra utiliser randint(a, b)
qui renvoie un nombre pseudo-aléatoire entre a
et b
.
Il faut pour cela importer la fonction, par from random import randint
-
Vous pouvez aussi doter votre classe Balle
d'une méthode dessine
(qui affiche la balle), ainsi qu'une méthode bouge
qui la fait bouger.
Créez cette classe et instanciez une balle.
Correction
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53 | import pygame, sys
import time
from pygame.locals import *
from random import randint
# randint(0,10) -> nb aléatoire entre 0 et 10
LARGEUR = 640
HAUTEUR = 480
RAYON = 20
pygame.display.init()
fenetre = pygame.display.set_mode((LARGEUR, HAUTEUR))
fenetre.fill([0, 0, 0])
class Balle:
def __init__(self):
self.x = randint(0, LARGEUR)
self.y = randint(0, HAUTEUR)
self.dx = randint(2, 5)
self.dy = randint(2, 5)
self.couleur = (randint(0, 255), randint(0, 255), randint(0, 255))
self.taille = RAYON
def dessine(self):
pygame.draw.circle(fenetre, self.couleur, (self.x, self.y), self.taille)
def bouge(self):
self.x += self.dx
self.y += self.dy
if self.y < self.taille or self.y > HAUTEUR - self.taille:
self.dy = -self.dy
if self.x < self.taille or self.x > LARGEUR - self.taille:
self.dx = -self.dx
ma_balle = Balle()
while True:
fenetre.fill([0, 0, 0])
ma_balle.dessine()
ma_balle.bouge()
pygame.display.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.display.quit()
sys.exit()
time.sleep(0.05)
|
2.2 Plusieurs balles
L'idée est de stocker dans une liste sac_a_balles
un nombre déterminé de balles...
Correction
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 | import pygame, sys
import time
from pygame.locals import *
from random import randint
# randint(0,10) -> nb aléatoire entre 0 et 10
LARGEUR = 640
HAUTEUR = 480
RAYON = 20
NB_BALLES = 10
pygame.display.init()
fenetre = pygame.display.set_mode((LARGEUR, HAUTEUR))
fenetre.fill([0, 0, 0])
class Balle:
def __init__(self):
self.x = randint(0, LARGEUR)
self.y = randint(0, HAUTEUR)
self.dx = randint(2, 5)
self.dy = randint(2, 5)
self.couleur = (randint(0, 255), randint(0, 255), randint(0, 255))
self.taille = RAYON
def dessine(self):
pygame.draw.circle(fenetre, self.couleur, (self.x, self.y), self.taille)
def bouge(self):
self.x += self.dx
self.y += self.dy
if self.y < self.taille or self.y > HAUTEUR - self.taille:
self.dy = -self.dy
if self.x < self.taille or self.x > LARGEUR - self.taille:
self.dx = -self.dx
mon_sac_a_balles = [Balle() for _ in range(NB_BALLES)]
while True:
fenetre.fill([0, 0, 0])
for balle in mon_sac_a_balles:
balle.dessine()
balle.bouge()
pygame.display.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.display.quit()
sys.exit()
time.sleep(0.05)
|
2.3 Collision de toutes les balles
Il «suffit» , dans la méthode constructeur, de tester la collision de la balle self
avec chacune des balles de notre sac_a_balles
.
Correction
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68 | import pygame, sys
import time
from pygame.locals import *
from random import randint
# randint(0,10) -> nb aléatoire entre 0 et 10
LARGEUR = 640
HAUTEUR = 480
RAYON = 20
NB_BALLES = 10
pygame.display.init()
fenetre = pygame.display.set_mode((LARGEUR, HAUTEUR))
fenetre.fill([0, 0, 0])
class Balle:
def __init__(self):
self.x = randint(0, LARGEUR)
self.y = randint(0, HAUTEUR)
self.dx = randint(2, 5)
self.dy = randint(2, 5)
self.couleur = (randint(0, 255), randint(0, 255), randint(0, 255))
self.taille = RAYON
def dessine(self):
pygame.draw.circle(fenetre, self.couleur, (self.x, self.y), self.taille)
def bouge(self):
self.x += self.dx
self.y += self.dy
if self.y < self.taille or self.y > HAUTEUR - self.taille:
self.dy = -self.dy
if self.x < self.taille or self.x > LARGEUR - self.taille:
self.dx = -self.dx
for balle in mon_sac_a_balles:
if (
(self.x - balle.x) ** 2 + (self.y - balle.y) ** 2
) ** 0.5 < self.taille + balle.taille:
self.dx, balle.dx = balle.dx, self.dx
self.dy, balle.dy = balle.dy, self.dy
mon_sac_a_balles = []
for _ in range(NB_BALLES):
new_ball = Balle()
mon_sac_a_balles.append(new_ball)
# ces 4 dernières lignes peuvent s'écrire par une seule ligne en compréhension :
# mon_sac_a_balles = [Balle() for _ in range(NB_BALLES)]
while True:
fenetre.fill([0, 0, 0])
for balle in mon_sac_a_balles:
balle.dessine()
balle.bouge()
pygame.display.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.display.quit()
sys.exit()
time.sleep(0.05)
|
3. Extensions
- Vous pouvez créer des balles de couleurs identiques, sauf une. Cette balle diffusera sa couleur à toutes les balles avec qui elle rentrera en collision.
- En la supprimant de la liste
sac_a_balles
, vous pouvez faire disparaitre une balle.
- Vous pouvez créer une balle que vous déplacerez au clavier (voir ici pour la gestion des déplacements)
- ...
4. Organisation du projet
Calendrier du projet
- 16/09/2024 : démarrage du projet et constitution des groupes
- séances du jeudi 19/09/2024, 26/09/2024 et 03/10/2024 : travail sur le projet
- remise du projet sur Capytale : 03/10/2024 à 18h dernier délai
Dépôt de projet sur Capytale
Cliquez ici.
Servez-vous de cette feuille de projet pour y déposer les différentes versions de votre travail.
Je pourrai ainsi le consulter au fur et à mesure de votre progression.
Ce que je ne veux pas voir :
Groupes de projet
- groupe A : Yanis - Jean
- groupe B : Marilou
- groupe C : Allan - Sarah
- groupe D : Perine - Rita
- groupe E : Gansiry
- groupe F : Thomas - Etan - Titouan
- groupe G : Noah
- groupe H : Tesnime - Enzo
- groupe I : Malone - Bryan - Moustapha
- groupe J : Oscar - Owen - Mikhailo
- groupe K : Milan - Nathan