Tilemaps et scrolling en Pygame¶
Créez des niveaux complets comme dans les vrais jeux 2D en utilisant des tilemaps et une caméra avec scrolling en Pygame.
Jusqu’ici, vos niveaux étaient codés à la main, avec des rectangles placés directement dans le code. Ça fonctionne, mais dès qu’un niveau devient un peu grand, ça devient vite pénible.
Dans cette page, on va voir comment :
- construire un niveau à partir d’une tilemap
- faire un monde plus grand que l’écran
- ajouter une caméra (scrolling) qui suit le joueur
Qu’est-ce qu’une tilemap ?¶
Une tilemap, c’est :
- une grille
- composée de tuiles (tiles)
- toutes de la même taille
Chaque case de la grille représente un type de terrain.
Représenter une tilemap en Python¶
La méthode la plus simple est une liste de chaînes de caractères.
level = [
"############################",
"#............#.............#",
"#.######.#####.#.#####.####.#",
"#.#....#.....#.#.....#....#.#",
"#.#.##.#####.#.#####.##.##.#.#",
"#...##.......#.......##....#",
"############################",
]
Ici :
#= mur.= chemin libre
Taille des tiles¶
Toutes les tiles ont la même taille :
TILE_SIZE = 32
La position d’une tile (x, y) dans la grille devient :
pixel_x = x * TILE_SIZE
pixel_y = y * TILE_SIZE
Générer les murs depuis la tilemap¶
On parcourt la grille et on crée un Rect pour chaque mur.
walls = []
for y, row in enumerate(level):
for x, tile in enumerate(row):
if tile == "#":
rect = pygame.Rect(
x * TILE_SIZE,
y * TILE_SIZE,
TILE_SIZE,
TILE_SIZE
)
walls.append(rect)
Un monde plus grand que l’écran¶
Le labyrinthe est plus large que la fenêtre. On ne peut pas tout afficher en même temps.
Il faut donc une caméra.
Principe du scrolling¶
On ne déplace pas l’écran. On décale l’affichage du monde par rapport au joueur.
camera_x = player.rect.centerx - SCREEN_WIDTH // 2
camera_x = max(0, camera_x)
Dessiner avec la caméra¶
Pour dessiner un objet du monde :
screen_x = rect.x - camera_x
Les collisions, elles, restent en coordonnées réelles.
Exemple complet : labyrinthe avec scrolling¶
Ce jeu :
- génère un labyrinthe avec une tilemap
- permet de déplacer un joueur bleu
- empêche de traverser les murs
- utilise une caméra horizontale simple
Code complet¶
import pygame
# --- CONFIG ---
SCREEN_WIDTH = 400
SCREEN_HEIGHT = 320
TILE_SIZE = 32
FPS = 60
# --- LEVEL ---
level = [
"############################",
"#............#.............#",
"#.######.#####.#.#####.#####",
"#.#....#.......#.....#....##",
"#.#.##.#####.#.#####.##.####",
"#.#.##.##....#.......##....#",
"#...##....##.#.......##....#",
"#.####.#####...#####.##.##.#",
"#......#####################",
"############################",
]
LEVEL_WIDTH = len(level[0]) * TILE_SIZE
# INIT
pygame.init()
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
clock = pygame.time.Clock()
# PLAYER
class Player:
def __init__(self):
self.rect = pygame.Rect(36, 36, 20, 20)
self.speed = 4
def move(self, dx, dy, walls):
self.rect.x += dx
for wall in walls:
if self.rect.colliderect(wall):
if dx > 0:
self.rect.right = wall.left
if dx < 0:
self.rect.left = wall.right
self.rect.y += dy
for wall in walls:
if self.rect.colliderect(wall):
if dy > 0:
self.rect.bottom = wall.top
if dy < 0:
self.rect.top = wall.bottom
def update(self, keys, walls):
dx = dy = 0
if keys[pygame.K_LEFT]:
dx = -self.speed
if keys[pygame.K_RIGHT]:
dx = self.speed
if keys[pygame.K_UP]:
dy = -self.speed
if keys[pygame.K_DOWN]:
dy = self.speed
self.move(dx, dy, walls)
def draw(self, screen, camera_x):
pygame.draw.rect(
screen,
(0, 100, 255),
self.rect.move(-camera_x, 0)
)
# WALLS
walls = []
for y, row in enumerate(level):
for x, tile in enumerate(row):
if tile == "#":
walls.append(
pygame.Rect(
x * TILE_SIZE,
y * TILE_SIZE,
TILE_SIZE,
TILE_SIZE
)
)
player = Player()
# GAME LOOP
running = True
while running:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
keys = pygame.key.get_pressed()
player.update(keys, walls)
# caméra
camera_x = player.rect.centerx - SCREEN_WIDTH // 2
camera_x = max(0, min(camera_x, LEVEL_WIDTH - SCREEN_WIDTH))
# draw
screen.fill((30, 30, 30))
for wall in walls:
pygame.draw.rect(
screen,
(200, 200, 200),
wall.move(-camera_x, 0)
)
player.draw(screen, camera_x)
pygame.display.flip()
pygame.quit()
Quizz¶
wall.move(-camera_x, 0)
Pourquoi applique-t-on `-camera_x` uniquement à l’affichage ?
- Pour déplacer le joueur
- *Pour décaler l’affichage sans casser les collisions
- Pour gérer la gravité
- Pour accélérer le jeu
> La caméra sert uniquement à l’affichage. La logique du jeu reste en coordonnées réelles.
Exercice pratique¶
Modifiez votre Mario de manière à lui ajouter une tilemap.
En résumé¶
Vous savez maintenant :
- Créer un niveau avec une tilemap
- Générer automatiquement les murs
- Implémenter un scrolling simple
- Gérer des collisions dans un monde plus grand que l’écran
- Séparer logique du jeu et affichage
Avec ça, vous avez les bases utilisées dans la majorité des jeux 2D professionnels.