Aller au contenu

TP : balles rebondissantes⚓︎

image

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

image

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 ?

image

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