Tutoriel Pyxel

Objectif : préparer un dossier de projet, créer des images avec l’éditeur Pyxel, charger un fichier .pyxres, afficher un sprite, déplacer un personnage avec les flèches, placer des murs via une grille (liste de listes) et empêcher les collisions.


Créer l’arborescence minimale

  • Créer un dossier de projet dans votre espace personnel : P:\Documents\NSI\Projet_Pyxel\
  • Placer dans ce dossier deux fichiers Python :
    • editor.py : qui ouvrira l’éditeur Pyxel
    • main.py : qui lancera le programme de jeu
  • L’éditeur créera un fichier de ressources :
    • my_resource.pyxres

Créer le script d’ouverture de l’éditeur

Créer editor.py avec le code suivant :

import sys, runpy

# Simuler la commande : pyxel edit my_resource.pyxres
sys.argv = ["pyxel", "edit", "my_resource.pyxres"]
runpy.run_module("pyxel.__main__", run_name="__main__")

Lancer l’éditeur

  • Exécuter editor.py dans Thonny.
  • Observer l’ouverture de la fenêtre de l’éditeur Pyxel.
  • Conserver cette fenêtre ouverte le temps de dessiner, puis enregistrer.

Comprendre ce qu’est un fichier .pyxres

  • Considérer my_resource.pyxres comme une “boîte” qui contient des ressources :
    • Images (sprites) dans des banques d’images (Image 0, Image 1, etc.)
    • Tilemaps (cartes composées de tuiles)
    • Sons et musiques (optionnel)

Le programme Python chargera ce fichier avec l’instruction :

pyxel.load("my_resource.pyxres")

Comprendre l’onglet Image

  • Se placer dans l’onglet Image.
  • Sélectionner Image 0 (banque d’image numéro 0).
  • Observer la grande zone de pixels avec une grille.
  • Interpréter les coordonnées :
    • u : coordonnée horizontale dans l’image (vers la droite)
    • v : coordonnée verticale dans l’image (vers le bas)
    • (u, v) = (0, 0) : pixel en haut à gauche de la banque d’image

Comprendre l’idée de “sprite”

  • Un sprite correspond à un rectangle dans l’image.
  • Exemple : un sprite 16×16 placé en haut à gauche occupe :
    • u de 0 à 15
    • v de 0 à 15

Comprendre le lien entre l’éditeur et le code

Pour dessiner à l’écran un sprite stocké dans l’image 0, utiliser :

pyxel.blt(x, y, img, u, v, w, h, colkey=0)
  • x, y : position en pixels dans la fenêtre du jeu
  • img : numéro de banque d’image (souvent 0)
  • u, v : coordonnées dans l’image (ce qui est dessiné)
  • w, h : largeur et hauteur du sprite en pixels
  • colkey=0 : rendre la couleur 0 transparente (souvent le noir)

Dessiner un personnage 16×16

  • Dans l’onglet Image, sur Image 0 :
    • Dessiner un personnage dans un carré 16×16 à partir de (u=0, v=0)
    • Utiliser un fond en couleur 0 (noir) si la transparence est souhaitée
  • Enregistrer le fichier :
    • Menu File → Save (ou raccourci clavier)
  • Vérifier l’existence de my_resource.pyxres dans le dossier du projet.

Objectif

  • Ouvrir une fenêtre noire.
  • Charger les ressources dessinées.
  • Afficher le personnage.
  • Déplacer le personnage avec les flèches.
  • Empêcher le personnage de sortir de la fenêtre.

Créer main.py

Créer main.py avec le code suivant :

import pyxel

# Définir la taille de la fenêtre
W, H = 160, 120

# Définir la position initiale du personnage
x, y = 50, 50

# Définir la vitesse de déplacement (pixels par frame)
speed = 2

# Initialiser la fenêtre Pyxel
pyxel.init(W, H, title="Deplacement simple")

# Charger le fichier de ressources créé par l'éditeur
pyxel.load("my_resource.pyxres")

def update():
    """
    Mettre à jour l'état du jeu.
    Lire le clavier.
    Calculer la nouvelle position du personnage.
    Appliquer des limites pour rester dans la fenêtre.
    """
    global x, y

    # Modifier x et y selon les touches pressées
    if pyxel.btn(pyxel.KEY_LEFT):
        x = x - speed
    if pyxel.btn(pyxel.KEY_RIGHT):
        x = x + speed
    if pyxel.btn(pyxel.KEY_UP):
        y = y - speed
    if pyxel.btn(pyxel.KEY_DOWN):
        y = y + speed

    # Empêcher la sortie de l'écran (sprite 16x16)
    if x < 0:
        x = 0
    if x > W - 16:
        x = W - 16
    if y < 0:
        y = 0
    if y > H - 16:
        y = H - 16

def draw():
    """
    Dessiner l'écran.
    Effacer l'écran en noir.
    Dessiner le personnage.
    """
    # Remplir l'écran avec la couleur 0 (noir)
    pyxel.cls(0)

    # Dessiner le sprite du personnage
    # sprite 16x16 dans l'image 0 à (u=0, v=0)
    pyxel.blt(x, y, 0, 0, 0, 16, 16, colkey=0)

# Lancer la boucle Pyxel : update puis draw répétés
pyxel.run(update, draw)

Détailler l’algorithme de déplacement

  • Initialiser la position (x, y) du personnage.
  • À chaque frame (boucle de jeu) :
    1. Lire l’état des touches avec pyxel.btn(...).
    2. Si une touche est pressée, modifier x ou y :
      • gauche : diminuer x
      • droite : augmenter x
      • haut : diminuer y
      • bas : augmenter y
    3. Appliquer des bornes pour rester à l’écran :
      • forcer x à rester entre 0 et W-16
      • forcer y à rester entre 0 et H-16
  • À chaque frame :
    1. Effacer l’écran avec pyxel.cls(0).
    2. Dessiner le sprite avec pyxel.blt(...).

Objectif

  • Dessiner un bloc “mur” 16×16 dans la même banque d’image (Image 0).
  • Placer ce mur juste à droite du personnage dans l’image.

Dessiner le mur

  • Ouvrir l’éditeur via editor.py.
  • Dans l’onglet Image, sur Image 0 :
    • Laisser le personnage dans le carré 16×16 à (0,0)
    • Dessiner un mur dans un carré 16×16 à partir de (u=16, v=0)
      • le mur occupe u de 16 à 31 et v de 0 à 15
  • Enregistrer my_resource.pyxres.

Principe de la grille (liste de listes)

  • Découper la fenêtre en cases de 16×16.
  • Pour une fenêtre 160×120 :
    • Colonnes : 160 / 16 = 10
    • Lignes : 120 / 16 = 7
  • Représenter la carte avec une liste de 7 lignes, chacune contenant 10 valeurs :
    • 0 : case vide
    • 1 : case mur

Placer exactement 6 murs

Définir une grille qui contient 6 valeurs 1. Exemple ci-dessous.


Principe de la collision

  • Associer une position en pixels (x, y) à une case (col, row) :
    • col = x // 16
    • row = y // 16
  • Un personnage 16×16 occupe plusieurs pixels.
  • Tester les 4 coins du personnage :
    • coin haut-gauche
    • coin haut-droite
    • coin bas-gauche
    • coin bas-droite
  • Refuser un déplacement si au moins un coin tombe dans une case mur.

Utiliser un déplacement “fluide”

  • Calculer une position candidate (nx, ny) à partir du clavier.
  • Tester le déplacement sur l’axe X puis sur l’axe Y :
    • autoriser X si pas de collision
    • autoriser Y si pas de collision

Remplacer main.py par :

import pyxel

TILE = 16
W, H = 160, 120
COLS = W // TILE   # 10
ROWS = H // TILE   # 7

# Grille : 0 = vide, 1 = mur
# Placer exactement 6 murs (6 valeurs à 1)
level = [
    [0,0,0,0,0,0,0,0,0,0],
    [0,0,0,1,0,0,0,0,0,0],  # mur 1
    [0,0,0,1,0,0,0,1,0,0],  # mur 2 et mur 3
    [0,0,0,0,0,0,0,1,0,0],  # mur 4
    [0,0,0,0,1,0,0,0,0,0],  # mur 5
    [0,0,0,0,1,0,0,0,0,0],  # mur 6
    [0,0,0,0,0,0,0,0,0,0],
]

# Position initiale du personnage
px, py = 32, 32
speed = 2

pyxel.init(W, H, title="Murs et collisions")
pyxel.load("my_resource.pyxres")

def is_wall_at_pixel(x, y):
    """
    Déterminer si le pixel (x, y) tombe sur une case mur.
    Convertir pixel -> case via division entière.
    Considérer l'extérieur de la fenêtre comme un mur.
    """
    col = x // TILE
    row = y // TILE

    if col < 0 or col >= COLS or row < 0 or row >= ROWS:
        return True

    return level[row][col] == 1

def can_move_to(nx, ny):
    """
    Déterminer si un sprite 16x16 placé en (nx, ny) toucherait un mur.
    Tester les 4 coins du sprite.
    """
    corners = [
        (nx, ny),
        (nx + 15, ny),
        (nx, ny + 15),
        (nx + 15, ny + 15),
    ]

    for cx, cy in corners:
        if is_wall_at_pixel(cx, cy):
            return False

    return True

def update():
    """
    Lire le clavier.
    Proposer une nouvelle position.
    Tester la collision.
    Appliquer le mouvement seulement si autorisé.
    """
    global px, py

    nx, ny = px, py

    # Construire une position candidate
    if pyxel.btn(pyxel.KEY_LEFT):
        nx = nx - speed
    if pyxel.btn(pyxel.KEY_RIGHT):
        nx = nx + speed
    if pyxel.btn(pyxel.KEY_UP):
        ny = ny - speed
    if pyxel.btn(pyxel.KEY_DOWN):
        ny = ny + speed

    # Appliquer X puis Y (meilleur ressenti en collision)
    if can_move_to(nx, py):
        px = nx
    if can_move_to(px, ny):
        py = ny

def draw():
    """
    Effacer l'écran.
    Dessiner les murs selon la grille.
    Dessiner le personnage.
    """
    pyxel.cls(0)

    # Dessiner les murs : sprite 16x16 en (u=16, v=0)
    for row in range(ROWS):
        for col in range(COLS):
            if level[row][col] == 1:
                x = col * TILE
                y = row * TILE
                pyxel.blt(x, y, 0, 16, 0, 16, 16)

    # Dessiner le personnage : sprite 16x16 en (u=0, v=0)
    pyxel.blt(px, py, 0, 0, 0, 16, 16, colkey=0)

pyxel.run(update, draw)

Modifier l’emplacement des 6 murs

  • Déplacer les 1 dans la grille level.
  • Conserver exactement 6 murs.
  • Vérifier que le personnage ne traverse pas les murs.

Modifier l’apparence du mur

  • Retoucher le sprite du mur dans l’éditeur en restant dans le carré 16×16 à (16,0).
  • Sauvegarder puis relancer main.py.

Ajouter un autre élément

  • Créer un nouveau sprite dans l’éditeur dans le carré 16×16 à (32,0).
  • Sauvegarder.
  • Ajouter des 2 dans la grille level du fichier main.py.
  • Faire correspondre le nouveau sprite à ces nombres.