Ce que vous allez apprendre
Ce chapitre ajoute du texte a votre jeu : score, vies, niveau, et messages de fin de partie. Vous allez decouvrir le systeme de polices de Pygame, comment afficher du texte a l'ecran, et comment creer une interface utilisateur (HUD) complete.
Le concept
En Pygame, le texte n'est pas dessine directement : il est d'abord converti en une Surface (une image), puis colle (blit) sur l'ecran. Le processus en deux etapes est le suivant :
- Creer une police :
pygame.font.SysFont(nom, taille)charge une police installee sur le systeme. - Rendre le texte :
font.render(texte, antialias, couleur)cree une Surface contenant le texte.
Le parametre antialias (True/False) active le lissage des bords. Utilisez toujours True pour un rendu agreable.
Pour centrer un texte, on utilise surface.get_width() pour connaitre sa largeur en pixels, puis on calcule la position :
x = SCREEN_WIDTH // 2 - surface.get_width() // 2
Etats du jeu
Ce chapitre introduit aussi la notion d'etats du jeu. Notre jeu a maintenant trois etats possibles :
STATE_PLAYING: le jeu est en coursSTATE_GAME_OVER: le joueur a perdu toutes ses viesSTATE_YOU_WIN: toutes les briques sont detruites
Seul l'etat PLAYING met a jour la logique du jeu. Les autres affichent un ecran de fin et attendent que le joueur appuie sur R pour recommencer.
Etape par etape
1. Creer les polices
On cree plusieurs polices de tailles differentes pour chaque usage : HUD, titres, messages.
hud_font = pygame.font.SysFont("monospace", 18)
big_font = pygame.font.SysFont("monospace", 48, bold=True)
medium_font = pygame.font.SysFont("monospace", 24)
small_font = pygame.font.SysFont("monospace", 14)
SysFont utilise les polices installees sur votre systeme. "monospace" est disponible partout. Le parametre bold=True active le gras.
2. Afficher le score, les vies et le niveau
On dessine le HUD (Head-Up Display) en haut de l'ecran a chaque frame. Le score est a gauche, les vies au centre, le niveau a droite.
# HUD
score_surf = hud_font.render(f"Score: {score}", True,
(255, 255, 100))
screen.blit(score_surf, (10, 10))
lives_surf = hud_font.render(f"Lives: {lives}", True,
(255, 100, 100))
screen.blit(lives_surf,
(SCREEN_WIDTH // 2 - lives_surf.get_width() // 2, 10))
level_surf = hud_font.render(f"Level: {level}", True,
(100, 200, 255))
screen.blit(level_surf,
(SCREEN_WIDTH - level_surf.get_width() - 10, 10))
3. Systeme de score par rangee
Les briques du haut valent plus de points que celles du bas. C'est plus juste, car elles sont plus difficiles a atteindre.
def score_for_row(row):
"""Higher rows (closer to top) give more points."""
return (BRICK_ROWS - row) * 10
La rangee 0 (tout en haut) vaut 60 points, la rangee 5 (en bas) vaut 10 points.
4. Systeme de vies
Le joueur commence avec 3 vies. Quand la balle tombe, il perd une vie. A zero vies, c'est le Game Over.
# Ball falls off bottom: lose a life
if ball_y - BALL_RADIUS > SCREEN_HEIGHT:
lives -= 1
if lives <= 0:
game_state = STATE_GAME_OVER
else:
reset_ball()
5. Ecrans de fin avec superposition
Pour les ecrans GAME OVER et YOU WIN, on dessine d'abord une surface semi-transparente noire par-dessus tout le jeu, puis on affiche le texte au centre.
if game_state == STATE_GAME_OVER:
overlay = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT),
pygame.SRCALPHA)
overlay.fill((0, 0, 0, 150))
screen.blit(overlay, (0, 0))
go_text = big_font.render("GAME OVER", True, (255, 60, 60))
screen.blit(go_text,
(SCREEN_WIDTH // 2 - go_text.get_width() // 2,
SCREEN_HEIGHT // 2 - 40))
Points cles a retenir
Astuce : ne creez pas de police a chaque frame ! SysFont() est lent. Creez vos polices une seule fois avant la boucle et reutilisez-les.
Piege courant : font.render() ne gere pas les retours a la ligne. Pour du texte multi-lignes, vous devez rendre chaque ligne separement et les positionner manuellement.
Fonctions Pygame utilisees :
pygame.font.SysFont(name, size): charge une police systemefont.render(text, antialias, color): cree une Surface de textesurface.get_width(): largeur en pixels (utile pour centrer)surface.get_height(): hauteur en pixels
Code complet
"""Brick Breaker - Chapter 8: Text and Fonts"""
import pygame
import sys
import math
pygame.init()
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
FPS = 60
BG_COLOR = (20, 20, 40)
PADDLE_WIDTH = 100
PADDLE_HEIGHT = 15
PADDLE_SPEED = 8
paddle_x = SCREEN_WIDTH // 2 - PADDLE_WIDTH // 2
paddle_y = SCREEN_HEIGHT - 40
BALL_RADIUS = 8
BALL_BASE_SPEED = 300
BALL_SPEED_INCREMENT = 10
BRICK_ROWS = 6
BRICK_COLS = 10
BRICK_WIDTH = 70
BRICK_HEIGHT = 20
BRICK_PADDING = 5
BRICK_OFFSET_X = 30
BRICK_OFFSET_Y = 50
MAX_LIVES = 3
lives = MAX_LIVES
score = 0
level = 1
# Game states
STATE_PLAYING = 0
STATE_GAME_OVER = 1
STATE_YOU_WIN = 2
game_state = STATE_PLAYING
def rainbow_color(row, total_rows):
hue = int(360 * row / total_rows)
color = pygame.Color(0)
color.hsva = (hue, 100, 100, 100)
return (color.r, color.g, color.b)
def create_paddle_image(width, height):
surf = pygame.Surface((width, height), pygame.SRCALPHA)
for y in range(height):
brightness = 200 + int(55 * (1 - y / height))
pygame.draw.line(surf, (brightness, brightness, brightness),
(0, y), (width, y))
pygame.draw.rect(surf, (0, 0, 0, 0), (0, 0, 3, 3))
pygame.draw.rect(surf, (0, 0, 0, 0), (width - 3, 0, 3, 3))
return surf.convert_alpha()
def create_ball_image(radius):
size = radius * 4
surf = pygame.Surface((size, size), pygame.SRCALPHA)
for r in range(radius * 2, radius, -1):
alpha = int(100 * (1 - (r - radius) / radius))
pygame.draw.circle(surf, (100, 100, 255, alpha),
(size // 2, size // 2), r)
pygame.draw.circle(surf, (255, 255, 255),
(size // 2, size // 2), radius)
pygame.draw.circle(surf, (255, 255, 255, 200),
(size // 2 - 2, size // 2 - 2), radius // 3)
return surf.convert_alpha()
def create_brick_image(width, height, color):
surf = pygame.Surface((width, height), pygame.SRCALPHA)
pygame.draw.rect(surf, color, (0, 0, width, height))
highlight = tuple(min(255, c + 50) for c in color)
pygame.draw.line(surf, highlight, (1, 1), (width - 2, 1))
pygame.draw.line(surf, highlight, (1, 1), (1, height - 2))
shadow = tuple(max(0, c - 50) for c in color)
pygame.draw.line(surf, shadow, (1, height - 1), (width - 1, height - 1))
pygame.draw.line(surf, shadow, (width - 1, 1), (width - 1, height - 1))
return surf.convert_alpha()
def create_bricks():
bricks = []
for row in range(BRICK_ROWS):
color = rainbow_color(row, BRICK_ROWS)
image = create_brick_image(BRICK_WIDTH, BRICK_HEIGHT, color)
for col in range(BRICK_COLS):
x = BRICK_OFFSET_X + col * (BRICK_WIDTH + BRICK_PADDING)
y = BRICK_OFFSET_Y + row * (BRICK_HEIGHT + BRICK_PADDING)
brick = {
"rect": pygame.Rect(x, y, BRICK_WIDTH, BRICK_HEIGHT),
"color": color,
"image": image,
"row": row,
}
bricks.append(brick)
return bricks
def reset_ball():
global ball_x, ball_y, ball_vx, ball_vy, ball_speed
ball_x = float(SCREEN_WIDTH // 2)
ball_y = float(SCREEN_HEIGHT // 2)
ball_speed = BALL_BASE_SPEED
ball_vx = ball_speed * math.cos(math.radians(-60))
ball_vy = ball_speed * math.sin(math.radians(-60))
def score_for_row(row):
"""Higher rows (closer to top) give more points."""
return (BRICK_ROWS - row) * 10
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Brick Breaker - Chapter 8")
clock = pygame.time.Clock()
# Fonts
hud_font = pygame.font.SysFont("monospace", 18)
big_font = pygame.font.SysFont("monospace", 48, bold=True)
medium_font = pygame.font.SysFont("monospace", 24)
small_font = pygame.font.SysFont("monospace", 14)
paddle_img = create_paddle_image(PADDLE_WIDTH, PADDLE_HEIGHT)
ball_img = create_ball_image(BALL_RADIUS)
bricks = create_bricks()
ball_x = float(SCREEN_WIDTH // 2)
ball_y = float(SCREEN_HEIGHT // 2)
ball_speed = BALL_BASE_SPEED
ball_vx = ball_speed * math.cos(math.radians(-60))
ball_vy = ball_speed * math.sin(math.radians(-60))
use_mouse = False
running = True
while running:
dt = clock.tick(FPS) / 1000.0
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
if event.key == pygame.K_m:
use_mouse = not use_mouse
if event.key == pygame.K_r and game_state != STATE_PLAYING:
lives = MAX_LIVES
score = 0
level = 1
bricks = create_bricks()
reset_ball()
game_state = STATE_PLAYING
if game_state == STATE_PLAYING:
if use_mouse:
mx, _ = pygame.mouse.get_pos()
paddle_x = mx - PADDLE_WIDTH // 2
else:
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] or keys[pygame.K_a]:
paddle_x -= PADDLE_SPEED
if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
paddle_x += PADDLE_SPEED
paddle_x = max(0, min(paddle_x, SCREEN_WIDTH - PADDLE_WIDTH))
# Move ball
ball_x += ball_vx * dt
ball_y += ball_vy * dt
# Bounce off left wall
if ball_x - BALL_RADIUS <= 0:
ball_x = BALL_RADIUS
ball_vx = abs(ball_vx)
ball_speed += BALL_SPEED_INCREMENT
# Bounce off right wall
if ball_x + BALL_RADIUS >= SCREEN_WIDTH:
ball_x = SCREEN_WIDTH - BALL_RADIUS
ball_vx = -abs(ball_vx)
ball_speed += BALL_SPEED_INCREMENT
# Bounce off top wall
if ball_y - BALL_RADIUS <= 0:
ball_y = BALL_RADIUS
ball_vy = abs(ball_vy)
ball_speed += BALL_SPEED_INCREMENT
# Ball falls off bottom: lose a life
if ball_y - BALL_RADIUS > SCREEN_HEIGHT:
lives -= 1
if lives <= 0:
game_state = STATE_GAME_OVER
else:
reset_ball()
# Ball vs paddle collision
paddle_rect = pygame.Rect(paddle_x, paddle_y,
PADDLE_WIDTH, PADDLE_HEIGHT)
ball_rect = pygame.Rect(ball_x - BALL_RADIUS,
ball_y - BALL_RADIUS,
BALL_RADIUS * 2, BALL_RADIUS * 2)
if ball_rect.colliderect(paddle_rect) and ball_vy > 0:
ball_y = paddle_y - BALL_RADIUS
hit_pos = (ball_x - paddle_x) / PADDLE_WIDTH
angle = 150 - hit_pos * 120
rad = math.radians(angle)
ball_speed += BALL_SPEED_INCREMENT
ball_vx = ball_speed * math.cos(rad)
ball_vy = -abs(ball_speed * math.sin(rad))
# Ball vs bricks collision
for brick in bricks[:]:
if ball_rect.colliderect(brick["rect"]):
score += score_for_row(brick["row"])
bricks.remove(brick)
br = brick["rect"]
if ball_x >= br.left and ball_x <= br.right:
ball_vy = -ball_vy
else:
ball_vx = -ball_vx
ball_speed += BALL_SPEED_INCREMENT
break
# Check if all bricks destroyed
if len(bricks) == 0:
game_state = STATE_YOU_WIN
# Draw
screen.fill(BG_COLOR)
for brick in bricks:
screen.blit(brick["image"], brick["rect"])
screen.blit(paddle_img, (paddle_x, paddle_y))
ball_offset = BALL_RADIUS * 2
screen.blit(ball_img, (ball_x - ball_offset, ball_y - ball_offset))
# HUD
score_surf = hud_font.render(f"Score: {score}", True, (255, 255, 100))
screen.blit(score_surf, (10, 10))
lives_surf = hud_font.render(f"Lives: {lives}", True, (255, 100, 100))
screen.blit(lives_surf,
(SCREEN_WIDTH // 2 - lives_surf.get_width() // 2, 10))
level_surf = hud_font.render(f"Level: {level}", True, (100, 200, 255))
screen.blit(level_surf,
(SCREEN_WIDTH - level_surf.get_width() - 10, 10))
# Game state overlays
if game_state == STATE_GAME_OVER:
overlay = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT),
pygame.SRCALPHA)
overlay.fill((0, 0, 0, 150))
screen.blit(overlay, (0, 0))
go_text = big_font.render("GAME OVER", True, (255, 60, 60))
screen.blit(go_text,
(SCREEN_WIDTH // 2 - go_text.get_width() // 2,
SCREEN_HEIGHT // 2 - 40))
final_score = medium_font.render(f"Final score: {score}",
True, (255, 255, 255))
screen.blit(final_score,
(SCREEN_WIDTH // 2 - final_score.get_width() // 2,
SCREEN_HEIGHT // 2 + 20))
restart = medium_font.render("Press R to restart",
True, (200, 200, 200))
screen.blit(restart,
(SCREEN_WIDTH // 2 - restart.get_width() // 2,
SCREEN_HEIGHT // 2 + 60))
elif game_state == STATE_YOU_WIN:
overlay = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT),
pygame.SRCALPHA)
overlay.fill((0, 0, 0, 150))
screen.blit(overlay, (0, 0))
win_text = big_font.render("YOU WIN!", True, (100, 255, 100))
screen.blit(win_text,
(SCREEN_WIDTH // 2 - win_text.get_width() // 2,
SCREEN_HEIGHT // 2 - 40))
final_score = medium_font.render(f"Final score: {score}",
True, (255, 255, 255))
screen.blit(final_score,
(SCREEN_WIDTH // 2 - final_score.get_width() // 2,
SCREEN_HEIGHT // 2 + 20))
restart = medium_font.render("Press R to restart",
True, (200, 200, 200))
screen.blit(restart,
(SCREEN_WIDTH // 2 - restart.get_width() // 2,
SCREEN_HEIGHT // 2 + 60))
info = small_font.render(
"[M] Mouse/Keyboard [Arrows] Move [R] Restart [ESC] Quit",
True, (150, 150, 150))
screen.blit(info, (10, SCREEN_HEIGHT - 25))
pygame.display.flip()
pygame.quit()
sys.exit()
Telechargement
Telecharger le code source de ce chapitre : brick-breaker-ch08.zip
