Ce que vous allez apprendre
Ce chapitre fait passer votre jeu des formes geometriques simples a des surfaces texturees. Vous allez decouvrir le concept central de Pygame : la Surface. Vous apprendrez a creer des images programmatiquement, a gerer la transparence (canal alpha), et a utiliser blit() pour dessiner une Surface sur une autre.
Le concept
En Pygame, tout est une Surface. L'ecran est une Surface. Une image chargee depuis un fichier est une Surface. Un texte rendu est une Surface. Une Surface est simplement un tableau de pixels en memoire.
Les operations cles sont :
pygame.Surface((w, h)): cree une nouvelle surface videpygame.Surface((w, h), pygame.SRCALPHA): cree une surface avec canal alpha (transparence par pixel)surface.convert(): optimise le format de la surface pour l'affichage (plus rapide a dessiner)surface.convert_alpha(): commeconvert()mais preserve le canal alphadestination.blit(source, position): copie la surface source sur la destination a la position donnee
En production, vous chargeriez des images depuis des fichiers PNG avec pygame.image.load("sprite.png"). Dans ce tutoriel, nous creons nos sprites programmatiquement pour ne dependre d'aucun fichier externe. Le principe est le meme : vous obtenez une Surface que vous dessinez avec blit().
Etape par etape
1. Creer l'image de la raquette
Notre raquette passe d'un simple rectangle a un degrade vertical. On cree une Surface avec SRCALPHA (transparence par pixel), puis on dessine ligne par ligne avec une luminosite qui varie.
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))
# Rounded corners effect
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()
La luminosite va de 255 (blanc, en haut) a 200 (gris clair, en bas), ce qui cree un effet 3D subtil. Les coins sont effaces avec des rectangles transparents (0, 0, 0, 0) pour un effet arrondi. L'appel a convert_alpha() a la fin optimise la Surface pour le dessin avec transparence.
2. Creer l'image de la balle
La balle gagne un halo lumineux autour d'elle. On dessine des cercles concentriques de plus en plus transparents, puis le noyau blanc, et enfin un petit reflet.
def create_ball_image(radius):
size = radius * 4
surf = pygame.Surface((size, size), pygame.SRCALPHA)
# Glow
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)
# Core
pygame.draw.circle(surf, (255, 255, 255),
(size // 2, size // 2), radius)
# Highlight
pygame.draw.circle(surf, (255, 255, 255, 200),
(size // 2 - 2, size // 2 - 2), radius // 3)
return surf.convert_alpha()
La Surface est plus grande que la balle elle-meme (radius * 4) pour laisser de la place au halo. Le halo est bleu clair et semi-transparent. Le reflet donne un effet de volume.
3. Creer les images des briques
Chaque brique gagne un effet 3D avec une ligne de surbrillance en haut et a gauche, et une ombre en bas et a droite.
def create_brick_image(width, height, color):
surf = pygame.Surface((width, height), pygame.SRCALPHA)
# Main color
pygame.draw.rect(surf, color, (0, 0, width, height))
# Highlight top and left
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 bottom and right
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()
On pre-calcule les images pour chaque rangee au demarrage du jeu, plutot que de les recreer a chaque frame. C'est une optimisation importante : creer des surfaces est couteux, les copier avec blit() est rapide.
4. Pre-creer les images au demarrage
# Pre-create images
paddle_img = create_paddle_image(PADDLE_WIDTH, PADDLE_HEIGHT)
ball_img = create_ball_image(BALL_RADIUS)
# Pre-create brick images for each row
brick_images = []
for row in range(BRICK_ROWS):
color = rainbow_color(row, BRICK_ROWS)
brick_images.append(create_brick_image(BRICK_WIDTH, BRICK_HEIGHT, color))
5. Dessiner avec blit()
Au lieu de pygame.draw.rect() et pygame.draw.circle(), on utilise maintenant screen.blit(image, position). La position est le coin superieur gauche de l'image.
# Draw bricks using images
for row in range(BRICK_ROWS):
for col in range(BRICK_COLS):
x = BRICK_OFFSET_X + col * (BRICK_WIDTH + BRICK_PADDING)
y = BRICK_OFFSET_Y + row * (BRICK_HEIGHT + BRICK_PADDING)
screen.blit(brick_images[row], (x, y))
# Draw paddle using image
screen.blit(paddle_img, (paddle_x, paddle_y))
# Draw ball using image (offset because the surface is larger)
ball_offset = BALL_RADIUS * 2
screen.blit(ball_img, (ball_x - ball_offset, ball_y - ball_offset))
Attention au decalage de la balle : comme sa Surface est plus grande que le cercle visible (a cause du halo), on doit soustraire un decalage pour que le centre visuel corresponde a la position logique.
Points cles a retenir
Astuce : pour charger une image depuis un fichier, utilisez img = pygame.image.load("sprite.png").convert_alpha(). N'oubliez jamais convert() ou convert_alpha() : sans cette etape, le dessin sera beaucoup plus lent car Pygame devra convertir le format a chaque blit().
Piege courant : la position passee a blit() est le coin superieur gauche de la surface, pas son centre. Pour centrer un sprite, soustrayez la moitie de sa largeur et de sa hauteur.
Piege courant : si votre image a un fond noir au lieu d'etre transparente, vous avez probablement oublie pygame.SRCALPHA a la creation de la Surface, ou vous utilisez convert() au lieu de convert_alpha().
Fonctions Pygame utilisees :
pygame.Surface((w, h), pygame.SRCALPHA): surface transparentesurface.convert_alpha(): optimise avec transparencescreen.blit(surface, (x, y)): copie une surface sur une autrepygame.image.load(path): charge une image depuis un fichier
Code complet
"""Brick Breaker - Chapter 5: Displaying Images"""
import pygame
import sys
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_x = SCREEN_WIDTH // 2
ball_y = SCREEN_HEIGHT // 2
BRICK_ROWS = 6
BRICK_COLS = 10
BRICK_WIDTH = 70
BRICK_HEIGHT = 20
BRICK_PADDING = 5
BRICK_OFFSET_X = 30
BRICK_OFFSET_Y = 50
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)
# Create paddle image (gradient surface)
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))
# Rounded corners effect
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()
# Create ball image (circle with glow)
def create_ball_image(radius):
size = radius * 4
surf = pygame.Surface((size, size), pygame.SRCALPHA)
# Glow
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)
# Core
pygame.draw.circle(surf, (255, 255, 255),
(size // 2, size // 2), radius)
# Highlight
pygame.draw.circle(surf, (255, 255, 255, 200),
(size // 2 - 2, size // 2 - 2), radius // 3)
return surf.convert_alpha()
# Create brick image
def create_brick_image(width, height, color):
surf = pygame.Surface((width, height), pygame.SRCALPHA)
# Main color
pygame.draw.rect(surf, color, (0, 0, width, height))
# Highlight top
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 bottom
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()
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Brick Breaker - Chapter 5")
clock = pygame.time.Clock()
font = pygame.font.SysFont("monospace", 14)
# Pre-create images
paddle_img = create_paddle_image(PADDLE_WIDTH, PADDLE_HEIGHT)
ball_img = create_ball_image(BALL_RADIUS)
# Pre-create brick images for each row
brick_images = []
for row in range(BRICK_ROWS):
color = rainbow_color(row, BRICK_ROWS)
brick_images.append(create_brick_image(BRICK_WIDTH, BRICK_HEIGHT, color))
use_mouse = False
running = True
while running:
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 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))
screen.fill(BG_COLOR)
# Draw bricks using images
for row in range(BRICK_ROWS):
for col in range(BRICK_COLS):
x = BRICK_OFFSET_X + col * (BRICK_WIDTH + BRICK_PADDING)
y = BRICK_OFFSET_Y + row * (BRICK_HEIGHT + BRICK_PADDING)
screen.blit(brick_images[row], (x, y))
# Draw paddle using image
screen.blit(paddle_img, (paddle_x, paddle_y))
# Draw ball using image
ball_offset = BALL_RADIUS * 2
screen.blit(ball_img, (ball_x - ball_offset, ball_y - ball_offset))
info = font.render("[M] Mouse/Keyboard [Arrows] Move [ESC] Quit",
True, (150, 150, 150))
screen.blit(info, (10, SCREEN_HEIGHT - 25))
pygame.display.flip()
clock.tick(FPS)
pygame.quit()
sys.exit()
Telechargement
Telecharger le code source de ce chapitre : brick-breaker-ch05.zip
