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
self.angle = 0
@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.centerx
def get_y(self):
return self.rect.centery
def move(self, dx, dy):
self.rect.move_ip(dx, dy)
def set_position(self, x, y):
self.rect.centerx = x
self.rect.centery = 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.original_image = pygame.Surface((0, 0))
self.rect = self.image.get_rect()
def set_angle(self, angle):
self.angle = angle
self.image = pygame.transform.rotate(self.original_image, angle)
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)
def kill(self):
self.image = pygame.Surface((0,0))
self.rect = self.image.get_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():
pressed_list = []
keys = pygame.key.get_pressed()
# 1. La boucle pour les touches "standards" (lettres, chiffres)
# On itère sur les scancodes standards
for scancode in range(len(keys)):
if keys[scancode]:
try:
name = pygame.key.name(scancode).upper()
# On filtre un peu pour éviter d'avoir des trucs bizarres ou vides
if name and name != "UNKNOWN":
pressed_list.append(name)
except ValueError:
pass
# 2. Vérification MANUELLE des touches spéciales (Flèches, Ctrl, etc.)
# C'est nécessaire car elles échappent souvent à la boucle simple ci-dessus
if keys[pygame.K_UP]:
pressed_list.append("UP")
if keys[pygame.K_DOWN]:
pressed_list.append("DOWN")
if keys[pygame.K_LEFT]:
pressed_list.append("LEFT")
if keys[pygame.K_RIGHT]:
pressed_list.append("RIGHT")
# Tu peux aussi ajouter d'autres touches spéciales souvent manquées par la boucle :
if keys[pygame.K_LSHIFT] or keys[pygame.K_RSHIFT]:
pressed_list.append("SHIFT")
if keys[pygame.K_SPACE]:
# Parfois "SPACE" ressort comme " " (vide) dans le name(), donc utile de le forcer
if " " in pressed_list: pressed_list.remove(" ") # Nettoyage optionnel
if "SPACE" not in pressed_list: pressed_list.append("SPACE")
# 3. Souris (Ton code original)
mouse_buttons = pygame.mouse.get_pressed()
if mouse_buttons[0]: pressed_list.append("MOUSE_1")
if mouse_buttons[1]: pressed_list.append("MOUSE_2")
if mouse_buttons[2]: pressed_list.append("MOUSE_3")
return pressed_list
try:
await main()
finally:
pygame.quit()
Projet Space Shooter#
Partie 1 : fonctionnalités de base#
Créez et dessinez l'acteur principal vaisseau en bas au centre de l'écran avec l'image spaceship.png. Rappel : l'écran fait 600x600 pixels.
Permettez au vaisseau de se déplacer latéralement.
Dans la boucle de jeu, récupérez les touches pressées avec
get_pressed_keys().Si la touche A est pressée, déplacez le vaisseau vers la gauche avec
move().Si la touche D est pressée, déplacez le vaisseau vers la droite avec
move().Ajustez la vitesse de déplacement pour que la maniabilité soit agréable.
Armons notre vaisseau ! Pour pouvoir tirer plusieurs fois, nous allons stocker nos projectiles dans une liste.
Avant la boucle
while True, créez une liste videlasers = [].Dans la boucle de jeu, si la touche
"SPACE"est pressée :Créez un nouvel acteur avec l'image
laser.png. Sa position de départ doit correspondre à celle du vaisseau. Pour récupérer la position horizontale, utilisezvaisseau.get_x(). Ajustez manuellement la position verticale par rapport à votre vaisseau.Ajoutez ce laser à la liste
lasersavecappend().
Dans la boucle de jeu, dessinez
draw()tous les lasers de la liste grâce à une bouclefor.
Vous devriez alors voir des lasers apparaître à l'endroit de votre vaisseau lorsque vous appuyez sur la touche Espace. Attention : Pour l'instant, rester appuyé sur espace créera un flux continu de lasers, c'est normal.
Les lasers doivent maintenant se déplacer vers le haut.
Dans la boucle de jeu, parcourez la liste des lasers avec un
for.Déplacez chaque laser vers le haut (vitesse verticale négative, ex:
move(0, -7)).Pour éviter de ralentir l'ordinateur, si un laser sort par le haut de l'écran (y < -50), supprimez-le de la liste avec
remove(). Vous pouvez récupérer la position verticale d'un laser aveclaser.get_y().
Nous allons maintenant faire apparaître et tomber des astéroïdes régulièrement.
Avant la boucle de jeu, créez une liste vide
asteroides = [].Créez également un
timer_asteroides = Timer(1)et démarrez-le immédiatement avectimer_asteroides.start().Dans la boucle de jeu, vérifiez si le timer est terminé (
if timer_asteroides.is_finished():).Si c'est le cas :
Créez un nouvel acteur
asteroide.pngen dessus de l'écran (y=-50).Ajoutez cet acteur à la liste
asteroides.Redémarrez le timer.
Enfin, en vous inspirant grandement de ce que vous avez fait pour les lasers, utilisez une boucle
fordans la boucle de jeu de sorte à dessiner les astéroïdes et à les faire bouger vers le bas. Vous devriez voir une succession d'astéroïdes tomber du haut de l'écran les uns sous les autres.
Pour l'instant les astéroïdes apparaissent tous à la même position horizontaale. Nous allons remédier à cela.
Modifiez la création d'astéroïde de l'étape précédente. Plutôt que de donner une position horizontale fixe lors de la création de l'acteur, utilisez randint(20, 580). Cette fonction permet de tirer un nombre entier aléatoire entre les deux bornes données en paramètre.
Vous devriez maintenant voir une pluie d'astéroïdes tomber du haut de l'écran.
Nous allons nous lancer dans une étape assez complexe : gérer la collision entre un laser et un astéroïde. Il faut vérifier chaque laser contre chaque astéroïde.
Dans la boucle de jeu, créez une boucle
forpour parcourir chaque laser.À l'intérieur de celle-ci, créez une deuxième boucle
forpour parcourir les astéroïdes.Vérifiez la collision :
if laser.collide(asteroide):. S'il y a bien une collision alors :Supprimez l'astéroïde de sa liste.
Supprimez le laser de sa liste.
Pour éviter que le joueur puisse continuellement tirer des lasers, nous allons maintenant revenir sur leur tir afin de le limiter.
Avant la boucle de jeu, créez un nouveau
timer_tiravec une durée de 0.5 secondes. Démarrez immédiatement ce timer.Modifiez la condition d'apparition d'un nouveau laser. On ne peut tirer que si la touche
"SPACE"est pressée ET que letimer_tirest fini.Dans cette condition, redémarrez le
timer_tir.
Votre vaisseau ne devrait maintenant plus tirer qu'un seul laser toutes les 0.5 secondes. Vous êtes évidemment libre d'adapter ce temps à votre convenance.
Nous allons maintenant gérer la fin du jeu. Celle-ci doit avoir lieu quand un astéroïde entre en collision avec le vaisseau ou lorsqu'un astéroïde n'a pas été détruit et continue sa route vers la terre.
Avant la boucle de jeu, créez une variable
gameover = False.Parcourez la liste des astéroïdes avec la boucle
forSi un astéroïde est un collision avec le vaisseau OU si la position verticale (rappelez-vous de
get_y()) d'un astéroïde est supérieure à 650, alors gameover devientTrue.
Modifiez votre code de sorte que tous les mouvements (vaisseau, lasers, astéroïdes) ainsi que toutes les apparitions (lasers et astéroïdes) ne se produisent plus que si gameover est
False.
En testant votre jeu, celui-ci devrait alors complètement se figer lorsqu'une des conditions de gameover est respectée.
Lorsque gameover est True, nous allons indiquer écrire le texte "GAMEOVER" à l'écran. Pour cela :
Avant la boucle
while True, créez un nouveauTextcomme ceci :txt_gameover = Text("GAMEOVER", 300, 50, True, (0,0,0), (255, 255, 255))Si gameover est
True, dessinezdraw()letxt_gameover.Modifiez les couleurs et les position du texte à votre convenance.
async def main():
while True:
await refresh((14, 0, 38))
Partie 2 : fonctionnalités intermédiaires#
Les fonctionnalités de la partie 2 valent au maximum 10 pts. Vous pouvez donc choisir des éléments de la liste suivante à implémenter et s'additionnant à 10 pts.
(2 pts) Ajoutez un score au jeu s'affichant avec le gameover. Par exemple, chaque astéroïde détruit augmente le score de 1, et, lors du gameover, le texte
Score : xoù x est remplacé par le véritable score est affiché.(2 pts) Accélérez graduellement la vitesse des astéroïdes. Actuellement, les astéroïdes se déplacent toujours à la même vitesse. A chaque fois que l'on détruit un astéroïde, leur vitesse pourrait augmenter afin de rendre le jeu progressivement plus difficile.
(2 pts) Accélérez la vitesse d'apparition des astéroïdes. Pour le moment, les astéroïdes apparaissent à un intervalle régulier. Afin d'augmenter progressivement la difficulté du jeu, réduisez petit à petit l'écart d'apparition entre chaque astéroïde (par exemple à chaque fois que le vaisseau en détruit un)
...
Partie 3 : fonctionnalités avancées#
...