Ce tuto fait suite au précédent : Tuto pyxel : Animer un personnage.
- Créer un fond constitué de divers éléments ;
- Animer le fond pour simuler un déplacement du personnage.
Préparation
Nous reprenons le code du tuto précédent comme base de travail, mais nous allons le réorganiser. Plusieurs nouveaux éléments vont en effet devoir être ajoutés au fur et à mesure du développement du jeu, et certains d’entre eux devront, comme le personnage, être animés ou voir certaines de leurs propriétés modifiées au cours du temps. Nous allons donc définir des classes pour chacun de ces éléments, afin de pouvoir les manipuler plus facilement et pour rendre notre code plus lisible. Chacune de ces classes comportera au moins trois méthodes : __init__ pour initialiser l’objet et ses paramètres de départ, puis update et draw. La première sera appelée à chaque frame pour mettre à jour les propriétés de l’objet, la seconde sera appelée pour afficher l’objet à l’écran.
Voici donc le code de départ, réorganisé avec la création d’une classe Personnage. Les méthodes update et draw de cette classe sont appelées depuis les méthodes update et draw de l’application.
import pyxel
class Personnage:
def __init__(self):
self.x = 0
self.y = pyxel.height - 16
self.direction = 1
self.marche = False
self.vitesse = 2
self.personnage_marche = False
def update(self):
if pyxel.btn(pyxel.KEY_LEFT):
self.x = max(self.x - self.vitesse, 0)
self.direction = -1
self.personnage_marche = True
if pyxel.btn(pyxel.KEY_RIGHT):
self.x = min(self.x + self.vitesse, pyxel.width - 16)
self.direction = 1
self.personnage_marche = True
def draw(self):
if self.personnage_marche:
if pyxel.frame_count % 6 < 3:
pyxel.blt(self.x, pyxel.height-16, 0, 0, 0, self.direction*16, 16)
else:
pyxel.blt(self.x, pyxel.height-16, 0, 16, 0, self.direction*16, 16)
self.personnage_marche = False
else:
pyxel.blt(self.x, pyxel.height-16, 0, 0, 0, self.direction*16, 16)
class App:
def __init__(self):
pyxel.init(256, 128, fps=30)
pyxel.load("jump_game.pyxres")
self.personnage = Personnage()
pyxel.run(self.update, self.draw)
def update(self):
self.personnage.update()
def draw(self):
pyxel.cls(12)
self.personnage.draw()
App()Fond statique : Montagne et ciel
L’objectif est de créer le fond de la fenêtre de jeu à l’aide de quatre composantes :
- une montagne : elle sera fixe et située au milieu de la fenêtre ;
- un ciel dégradé : il sera fixe et situé sous la montagne ;
- des nuages : ils seront animés, situés au-dessus de la montagne et défileront dans le ciel ;
- une forêt : elle sera animée, située derrière le personnage et défilera dans le bas de la fenêtre lorsque le personnage se déplace.
D’une manière générale, les objets statiques (dont la position ne change pas et dont les propriétés ne changent pas) peuvent être définis directement dans la classe App. Par contre, pour les objets dynamiques (soit parce qu’ils se déplacent, soit parce que leurs propriétés changent), on crée une classe indépendante dotée (au moins) des trois méthodes __init__, update et draw.
L’examen du fichier de ressources nous permet de déterminer les coordonnées de la zone de l’image correspondant à ces éléments.
- Montagne : le coin supérieur gauche a pour coordonnées 0 et 64, l’image a une largeur de 160 pixels et une hauteur de 24 pixels.
- Ciel : le coin supérieur gauche a pour coordonnées 0 et 88, l’image a une largeur de 160 pixels et une hauteur de 32 pixels.
On ajoute donc ces deux éléments à l’arrière plan de la fenêtre de jeu en modifiant comme suit la méthode draw de la classe App :
Explications :
- Ligne 4 :
pyxel.width//2 - 80est l’abscisse du coin supérieur gauche de l’image dans la fenêtre de jeu.pyxel.width//2est l’abscisse du milieu de la fenêtre,80est la moitié de la largeur de l’image. L’ordonnéepyxel.height-50est choisie par tâtonnement pour que la montagne soit bien positionnée verticalement dans la fenêtre. - Lignes 6 et 7 : la largeur de la fenêtre est 256 pixels, la largeur de l’image est 160 pixels, il faut donc répéter l’image deux fois pour remplir toute la largeur de la fenêtre. On a ajouté ici le dernier paramètre
12à la fonctionbltpour que la couleur 12 (bleu) soit transparente. Cela permet de voir la montagne à travers le ciel dégradé.
Comme le montre l’image ci-dessous, il faut aussi ajouter un dernier paramètre à la fonction blt pour que la couleur 12 (bleu) soit transparente également pour le personnage (dons dans la méthode draw de la classe Personnage).
Animation des nuages
Avec le temps qui passe, les nuages se déplacent lentement vers la gauche et d’autres nuages apparaissent sur la droite. Lorsqu’un nuage a complètement disparu de la fenêtre, il est retiré de la liste des nuages.
Nous allons définir une classe Nuage qui possède ses propres méthodes update et draw, comme le montre le code ci-dessous.
Explications :
- Ligne 8 :
if pyxel.frame_count % 10 == 0:permet de faire avancer le nuage de 1 pixel vers la gauche trois fois par seconde, puisque notre application a un framerate de 30 fps. - Ligne 5 : Un attribut booléen
replacedest ajouté à la classeNuagepour indiquer si le nuage a été remplacé par un nouveau nuage. Cela permet de ne pas créer un nouveau nuage à chaque frame (voir plus loin).
L’idée est de faire apparaître un nouveau nuage dans le ciel dès que le nuage précédent s’approche du bord gauche de l’écran. Pour cela, il sera nécessaire de définir une liste de nuages dans la classe App et de la mettre à jour à chaque frame. Voici le code modifié de la méthode __init__ de la classe App, dans lequel on initialise la liste des nuages avec un seul nuage placé au milieu de la fenêtre (lignes 6, 7 et 8).
def __init__(self): # class App
pyxel.init(256, 128, fps=30)
pyxel.load("jump_game.pyxres")
self.personnage = Personnage()
# liste des nuages visibles
self.nuages = []
premier_nuage = Nuage(x = pyxel.width//2-80, y = pyxel.height//4)
self.nuages.append(premier_nuage)
pyxel.run(self.update, self.draw)On modifie ensuite la méthode update de la classe App pour mettre à jour la liste des nuages et faire apparaître un nouveau nuage dès que le nuage précédent s’approche du bord gauche de la fenêtre.
Explications :
- Lignes 3 et 4 : la boucle
for nuage in self.nuagespermet de parcourir la liste des nuages et d’appliquer la méthodeupdateà chacun d’entre eux. - Lignes 5 à 8 : chaque nuage de la liste est mis à jour et, dès qu’un nuage a son abscisse inférieure à 10 (et qu’il n’a pas encore été remplacé), un nouveau nuage est créé. L’ancien nuage est alors marqué comme remplacé, afin d’éviter de créer un nouveau nuage à chaque frame :
nuage.replaced = True. - Lignes 9 et 10 : lorsque le nuage a complètement disparu de la fenêtre, il est retiré de la liste :
self.nuages.remove(nuage).
Dernière étape, on modifie la méthode draw de la classe App pour afficher tous les nuages de la liste.
def draw(self): # class App
pyxel.cls(12)
# Tracé de la montagne
pyxel.blt(pyxel.width//2 - 80, pyxel.height-50, 0, 0, 64, 160, 24)
# Tracé du ciel dégradé
pyxel.blt(0,pyxel.height-32, 0, 0, 88, 160, 32, 12)
pyxel.blt(160,pyxel.height-32, 0, 0, 88, 160, 32, 12)
# Tracé des nuages
for nuage in self.nuages:
nuage.draw()
self.personnage.draw()Animation de la forêt
Pour rendre plus réaliste le déplacement du personnage, on peut provoquer un déplacement de la forêt en sens contraire du déplacement du personnage. La montagne et le ciel, eux, peuvent rester fixes, car situés à une grande distance du personnage. Cela donne un effet de profondeur à la scène.
Dans le fichier de ressources, la forêt est constituée d’une image de largeur 160 pixels et de hauteur 16 pixels. Nous allons donc avoir besoin de répéter cette image pour remplir toute la largeur de la fenêtre. De plus, comme la forêt est animée, il faut prendre garde à créer une nouvelle copie de l’image à chaque fois que l’image précédente ne rempli plus totalement la largeur de la fenêtre.
Pour cela, on va créer une classe Foret qui possède ses propres méthodes update et draw, comme le montre le code ci-dessous.
class Foret:
def __init__(self):
self.x = 0
self.vitesse = 1
self.direction = 1
def update(self):
if pyxel.btn(pyxel.KEY_LEFT):
self.x = self.x + self.vitesse
self.direction = 1
if pyxel.btn(pyxel.KEY_RIGHT):
self.x = self.x - self.vitesse
self.direction = -1
def draw(self):
k = 0
while self.x - k*160 > -160 :
pyxel.blt(self.x - k*160,pyxel.height-16, 0, 0, 48, 160, 16, 12)
k = k + 1
k = 1
while self.x + k*160 < pyxel.width :
pyxel.blt(self.x + k*160,pyxel.height-16, 0, 0, 48, 160, 16, 12)
k = k + 1Explications :
- Ligne 3 :
self.xest l’abscisse du coin supérieur gauche de l’image dans la fenêtre de jeu. On initialise cette abscisse à 0. - Ligne 8 :
if pyxel.btn(pyxel.KEY_LEFT):permet de faire avancer la forêt vers la droite lorsque la toucheflèche gaucheest enfoncée. - Ligne 10 :
if pyxel.btn(pyxel.KEY_RIGHT):permet de faire avancer la forêt vers la gauche lorsque la toucheflèche droiteest enfoncée. - Lignes 17 à 23 : on utilise une boucle
whilepour répéter l’image de la forêt autant de fois que nécessaire pour remplir toute la largeur de la fenêtre. La variablekpermet de compter le nombre de répétitions nécessaires. On commence par répéter l’image vers la gauche, puis vers la droite.
Pour intégrer cet objet Foret à notre application, il faut maintenant ajouter une ligne à chacune des méthodes de la classe App :
Dans la méthode
__init__, on crée un objetForetet on l’ajoute à la liste des attributs de la classeApp.
Attention : il faut ajouter cette ligne après la ligne pyxel.init(256, 128, fps=30) et avant la ligne pyxel.run(self.update, self.draw).
Dans la méthode
update, on met à jour l’objetForet.Dans la méthode
draw, on affiche l’objetForet.
Attention : il faut ajouter cette ligne avant la ligne self.personnage.draw() : sinon, la forêt sera dessinée devant le personnage.


