import io
with redirect(stdout=io.StringIO()):
import tdoc.pygame
setup_canvas()
import pygame
import math
import pathlib
from random import randint, choice
width, height = 600, 600
window = pygame.display.set_mode((width, height))
pygame.init()
async def refresh(color):
await animation_frame()
pygame.display.flip()
window.fill(color)
class Actor(pygame.sprite.Sprite):
def __init__(self, image_path, cx, cy):
super().__init__()
self.original_image = pygame.image.load(image_path).convert_alpha()
self.image = self.original_image.copy()
self.rect = self.image.get_rect(center=(cx, cy))
self.collision_margin = 0.05 # 5% de marge
@property
def collision_rect(self):
"""Crée le rect de collision à la volée basé sur le rect actuel"""
return self.rect.inflate(
-self.rect.width * self.collision_margin,
-self.rect.height * self.collision_margin
)
def draw(self):
global window
window.blit(self.image, self.rect)
def get_x(self):
return self.rect.x
def get_y(self):
return self.rect.y
def move(self, dx, dy):
self.rect.move_ip(dx, dy) # Plus simple que x += dx, y += dy
def set_position(self, x, y):
self.rect.x = x
self.rect.y = y
def collide(self, other_actor):
other_rect = getattr(other_actor, 'collision_rect', other_actor.rect)
return self.collision_rect.colliderect(other_rect)
def flip(self, horizontal=True, vertical=False):
self.image = pygame.transform.flip(self.image, horizontal, vertical)
self.rect = self.image.get_rect(center=self.rect.center)
def scale(self, factor):
new_size = (
int(self.original_image.get_width() * factor),
int(self.original_image.get_height() * factor)
)
self.image = pygame.transform.scale(self.original_image, new_size)
self.rect = self.image.get_rect(center=self.rect.center)
def kill(self):
self.image = pygame.Surface((0, 0))
self.rect = self.image.get_rect()
class Text(pygame.sprite.Sprite):
def __init__(self, text, cx, cy, *args):
super().__init__()
self.font = pygame.font.Font(None, 36)
self.image = self.font.render(text, *args)
self.rect = self.image.get_rect()
self.rect.centerx, self.rect.centery = cx, cy
def draw(self):
global window
window.blit(self.image, self.rect)
class Timer(pygame.time.Clock):
def __init__(self, time):
super().__init__()
self.time = time * 1000
self.started = False
def start(self):
self.started = True
self.start_time = animation_time()
self.end_time = self.start_time + self.time
def is_finished(self):
return animation_time() >= self.end_time
def __str__(self):
if not self.started:
return "Not started"
elif self.is_finished():
return "0.00s"
else:
return f"{(self.end_time - animation_time()) / 1000:.2f}s"
def get_pressed_keys():
"""
Retourne la liste des touches et boutons de souris ACTUELLEMENT enfoncés.
"""
pressed_list = []
keys = pygame.key.get_pressed()
# On utilise len(keys) pour déterminer combien de scancodes il faut vérifier.
# C'est la méthode la plus fiable depuis Pygame 2.
for scancode in range(len(keys)):
if keys[scancode]: # Si l'état de la touche à cet index est True (appuyé)
try:
# Utiliser try/except est plus sûr car certains scancodes peuvent ne pas
# avoir de nom de touche lisible, mais ils devraient être rares.
nom_touche = pygame.key.name(scancode).upper()
pressed_list.append(nom_touche)
except ValueError:
# Gérer les scancodes inconnus si nécessaire, mais on peut juste les ignorer.
pass
mouse_buttons = pygame.mouse.get_pressed()
if mouse_buttons[0]: # Gauche
pressed_list.append("MOUSE_1")
if mouse_buttons[1]: # Molette
pressed_list.append("MOUSE_2")
if mouse_buttons[2]: # Droit
pressed_list.append("MOUSE_3")
return pressed_list
try:
await main()
finally:
pygame.quit()
async def main():
pygame.init()
pineapples = []
basket = Actor("basket.png", 300, 550)
game_over = False
score = 0
game_over_text = Text(f"Game over. Score: {score}", 300, 300, True, (10, 10, 10), (255, 90, 20))
while True:
await refresh((111, 255, 255))
for p in pineapples:
p.draw()
basket.draw()
actions = get_pressed_keys()
for action in actions:
if action == "QUIT":
running = False
elif action == "D":
basket.move(3, 0)
elif action == "A":
basket.move(-3, 0)
if game_over == False:
if randint(0, 100) == 0:
pineapple = Actor("orange.png", randint(0, width - 1), -100)
pineapples.append(pineapple)
for pineapple in pineapples:
pineapple.move(0, 3)
if pineapple.collide(basket):
score += 1
pineapple.kill()
pineapples.remove(pineapple)
if pineapple.rect.y > height:
game_over = True
else:
game_over_text.draw()
Pygame - TP 1#
Introduction#
Flappy Bird est un jeu vidéo mobile développé en 2013 au Vietnam par Hà Đông Nguyễn . Malgré son gameplay et son apparence très simple, ce jeu se hissera, en janvier 2014, à la première place des téléchargements de l'App Store et du Play Store. Grâce aux publicités intégrées, le développeur touchait jusqu'à 50'000$ par jour. De nombreuses controverses ont entouré Flappy Bird, notamment des accusations de plagiat avec ses tuyaux ressemblant étrangements à ceux des jeux Super Mario Bros. Hà Đông finit même par déclarer sur Twitter que ce jeu lui avait "ruiné sa vie".
Le but de ce TP sera de s'approprier les concepts de Pygame en redéveloppant notre propre version de Flappy Bird. Pour bien comprendre le concept de ce jeu, vous pouvez vous rendre sur flappybird.io pour en faire 2-3 parties.
Vous disposez des 3 images ci-dessous pour votre développement.
Objectifs et rendu#
Pour terminer ce TP vous devez :
Suivre les étapes 1-10 ci-dessous afin d'obtenir une première version jouable
Effectuer l'étape Personnalisation afin d'ajouter au moins un élément spécial au jeu
A la fin du TP, dans tous les cas, déposer votre code final du Flappy Bird ici.
Ces rendus comptent à faible coefficient dans votre moyenne d'informatique. Il est donc impératif que votre code soit votre propre production. Cela signifie que vous ne pouvez pas rendre un code pareil à celui d'un collègue, et que l'utilisation de l'IA est strictement interdite. Si vous utilisez des notions de Python autres que celles vues en cours, vous devez m'en informer avant le rendu. Tout manquement à ces règles entraine automatiquement la note de 1.
Création du jeu Flappy Bird#
Ajoutez et dessinez l'acteur Flappy Bird en haut à gauche de l'écran avec l'image bird.png.
Avant la boucle
while True, définissez une nouvelle variablevitessequi déterminera la vitesse verticale de l'oiseau. Si celle-ci est positive, alors l'oiseau tombera, si elle est négative, alors l'oiseau montera. Au début, la vitesse devrait être de1.Définissez une variable
VITESSE_MAXqui déterminera la vitesse maximale à laquelle l'oiseau peut tomber. Vous ajusterez cette valeur par la suite, mais vous pouvez y mettre4pour commencer.Définissez une variable
ACCELERATIONqui déterminera l'accélération de l'oiseau vers la Terre. Pour commencer, mettez-y la valeur0.05.Définissez une variable
VITESSE_SAUTqui déterminera la vitesse à laquelle l'oiseau "sautera" quand on appuiera sur une touche. Comme un saut fait monter l'oiseau, cette valeur doit être négative. Par exemple-3.
Complétez la boucle de jeu de sorte que l'oiseau tombe constamment vers le bas en fonction de la variable vitesse. Vous pouvez utiliser pour cela la fonction move().
A chaque tour de boucle de jeu, si la vitesse de l'oiseau est plus petite que la VITESSE_MAX, augmentez la valeur de VITESSE avec l' ACCELERATION. Même si cela est peu visible, vous devriez voir votre oiseau tomber de plus en plus vite vers le bas.
Afin de faire "sauter" l'oiseau, commencez par ajouter la ligne de code
touches_pressées = get_pressed_keys()à votre boucle de jeu. Vous récupérez ainsi la liste des touches pressées par le joueurAvec un
if ... in ..., contrôlez si l'utilisateur a appuyé sur la touche de saut. Vous pouvez choisir n'importe quelle touche du clavier, ou le clic gauche de la souris ("MOUSE_1").Si cette condition est respectée, la
vitessedoit prendre la valeur de la variableVITESSE_SAUT
Testez votre programme et ajustez les valeurs des 3 variables constantes (en majuscules) afin que l'effet de saut et de gravité vous convienne.
Nous allons maintenant faire apparaître des tuyaux toutes les 3 secondes. Pour cela, nous aurons besoin d'un Timer qui comptera le temps et permettra de déterminer le moment d'apparition des tuyaux.
Pour créer un nouveau Timer de 3 secondes, écrivez la ligne suivante avant la boucle de jeu :
timer_tuyaux = Timer(3)Afin d'instantanément démarrer ce Timer, faites suivre cette ligne de
timer_tuyaux.start().Dans la boucle de jeu, ajoutez un
print(timer_tuyaux)afin de vérifier que vous avez bel et bien un Timer qui commence à 3s et termine à 0s.
Si le Timer s'affiche correctement, vous pouvez retirer le print()
Vous pouvez contrôler si le
timer_tuyauxest terminé (c'est-à-dire est arrivé à 0.00s) avecif timer_tuyaux.is_finished():Ajoutez cette condition dans la boucle de jeu. Lorsqu'elle est respectée, faites un
print("Nouveaux tuyaux")et redémarrez le Timer avec la méthodestart()utilisée précédemment.Vous devriez alors voir le texte
Nouveaux tuyauxapparaître toutes les 3 secondes.
Il faut maintenant retirer ce print() et faire véritablement apparaître les tuyaux. Nous n'allons pas créer une variable individuelle pour chaque tuyau, mais allons les stocker dans une liste. Pour cela :
Avant la boucle de jeu, créez une liste vide
Quand le timer est terminé, créez 2 nouveau acteurs : un
tuyau_hautet untuyau_basavec les images adéquates. Pour le tuyau du haut, vous pouvez utilisertuyau_haut = Actor("tuyau_haut.png", 500, -100). Vous pouvez adapter cet exemple pour le tuyau du bas.Ajoutez ces 2 tuyaux à la liste avec
append()Pour que les tuyaux apparaissent, il faut les draw. Vous pouvez le faire dans la boucle de jeu avec la logique Pour chaque tuyau dans la liste : dessiner le tuyau.
3 secondes après le lancement, vous devriez voir une paire de tuyau apparaître.
Les tuyaux doivent se déplacer constamment vers la gauche.
Dans la boucle de jeu, ajoutez la logique suivante : Pour chaque tuyau dans la liste : move le tuyau vers la gauche. Vous devriez alors voir des tuyaux apparaître toutes les 3 secondes et se déplacer sans arrêt vers la gauche.
Quand un tuyau sort de l'écran à gauche, alors il doit être retiré de la liste. La logique est donc : *Pour chaque tuyau dans la liste : si la position horizontale du tuyau est inférieure à -100, alors retirer ce tuyau de la liste. Notez que si
tuyauest un acteur, alorstuyau.get_x()permet d'accéder à sa position horizontale. Utilisezremove()pour retirer un élément d'une liste.Modifiez la position initiale d'apparition des tuyaux pour qu'ils débutent en dehors de l'écran, à sa droite.
L'apparition et disparition des tuyaux devrait maintenant être parfaitement implémentée !
Le jeu doit maintenant s'arrêter lorsque Flappy Bird entre en collision avec un tuyau.
Créez une nouvelle variable
gameover = Falseavant la boucle de jeuDans la boucle de jeu, ajoutez la logique suivante : Pour chaque tuyau de la liste, si le Flappy Bird entre en collision avec ce tuyau, alors gameover devient True
Ajoutez un ou plusieurs
if gameover == False:dans votre programme de sorte à ce que l'apparition de tuyaux, leur mouvement, ainsi que le mouvement de l'oiseau n'aient plus lieu quand on perd.
A ce stade, le jeu est déjà jouable. Cependant, afin qu'il soit vraiment intéressant, certains éléments manquent. Choisissez des améliorations dans la liste ci-dessous et implémentez-en au moins une.
Les tuyaux n'apparaissent pas tous à la même hauteur, mais avec un décalage aléatoire en utilisant
randint()Un texte de gameover s'affiche quand on perd
Ce même texte de gameover contient un score correspondant au nombre de paires de tuyaux que l'on a esquivé
Appuyer sur une touche quand on a perdu permet de redémarrer le jeu
Le jeu ne démarre pas instantanément, mais seulement lorsqu'on appuie sur une touche
async def main():
while True:
await refresh((112, 196, 209))