Tableaux Maths
Slogan du site

Des cours, des exercices et des vidéos réalisées par des élèves.
Outils numériques en classe et découverte de la programmation.

Apprends à créer un jeu graphique (Kivy)

Apprends à créer un jeu pour rattraper des lapins. Tu apprendras à gérer : Des images, des déplacements, des collisions, un score, des animations, des sons et des écrans.

Article mis en ligne le 18 mai 2016
dernière modification le 5 août 2023

Le principe du jeu

Nous allons créer pas à pas un jeu dans lequel il faut rattraper des lapins tombant du ciel.

Le code sera complété et donné à chaque étape de la création.

Les étapes de créations sont les suivantes :

  • Un FloatLayout avec une image de fond"
  • Un Widget "Panier" avec une image
  • Déplacer le "Panier" en le touchant
  • Créer les Widget "Lapin"
  • Faire chuter le lapin
  • Multiplier les lapins
  • Tester la collision entre le panier et les lapins

A ce stade, le principe du jeu est fini. Mais pour édulcorer un peu cette application, nous verrons aussi :

  • Créer un label pour le score
  • Une animation au départ
  • Ajouter des sons
  • Créer un menu (gestion de plusieurs écrans)

Il vous restera à coder la fin du jeu c’est à dire une victoire ou une défaite.

Différentes étapes du jeu

Pour ceux qui ne veulent pas suivre ce cours, voici tous les fichiers dans un zip :

  • Le programme main.py
  • Les images
  • Un son
  • Le buildozer.spec pour passer sous Android
  • Le fichier .apk pour faire fonctionner ce jeu sous Android
L’ensemble du projet

Remarques :

  • Dans tout le processus, nous utiliserons une taille d’écran calculée dès le lancement du jeu avec Window.size afin de faire correspondre l’application aux différents supports.
  • En fonction du support (tablette, smarphone, PC...) il faudra peut-être modifier les paramètres de vitesse du jeu (horloge ou décalage vertical des lapins...)
  • N’oubliez pas de faire correspondre le nom des images téléchargées avec votre code !

Page suivante pour commencer en créant une image de fond.

++++

Une image de fond

Voici l’image à télécharger :

Le fond du jeu
Le fond du jeu des lapins

N’oubliez pas de faire correspondre le nom des images téléchargées avec votre code !

Notre jeu se déroulera sur un FloatLayout. Cela permet la position des différents Widget où l’on veut.

On construit le jeu (début) avant de l’afficher.

On récupère la taille de l’écran pour définir proportionnelement l’ensemble des objets.

Pour insérer l’image de fond, il suffit de l’ajouter au FloatLayout : add_widget avec des options d’étirement et d’échelle.

Le code :

As-tu compris ?

  • Change le fond avec une autre image
  • Change les options d’échelle et d’étirement pour observer l’effet.
  • Ajoute une image sur une autre image pour décorer ton jeu

Page suivante : Ajouter le panier

++++

Ajouter le Panier

Voici l’image :

Le panier du jeu

N’oubliez pas de faire correspondre le nom des images téléchargées avec votre code !

Le panier est un Widget (cela permet d’avoir des méthodes simples de déplacements et de tests de collisions). Il possède un Canvas pour accueillir l’image correspondante. (Une autre méthode est possible à la fin de cette section.)

Pour cela, on importe les librairies Widget (pour le contenant) et Rectangle (pour le contenu...l’image) :

Il faut donc faire correspondre la taille et la position de l’image avec le Widget. C’est pourquoi, dans la class "Panier", on définit en deux temps :

  • La taille et la position du Widget
  • Puis la taille et la position du rectangle accueillant l’image du panier dans le canvas

La ligne : self.size=(Window.size[0]*0.1,Window.size[1]*0.1) permet de définir la taille du panier à 10% de la taille du l’écran.

On n’oublie pas d’ajouter la création du panier dans le début du jeu et de lui renseigner le canvas du jeu : self.panier=Panier(self.canvas)

Le code à présent :

Remarque : Autre méthode

On aurait pu définir le panier directement en tant qu’image à déplacer, le code aurait été plus simple.
Dans le corps du Jeu :

Les images dans Kivy sont aussi des Widget.
Mais pour mieux comprendre la suite (les lapins), il est préférable d’étudier cette relation Widget et Canvas.

As-tu compris ?

  • Change l’image du panier
  • Change la position du panier
  • Ajoute un deuxième panier

Page suivante : Déplacer le panier avec le doigt (ou la souris).

++++

Déplacement du panier

On va utiliser une méthode méga pratique : on_touch_move(touch).
Elle permet de détecter si le doigt (ou la souris) opère des mouvements sur l’écran.
Le paramètre touch correspond aux coordonnées du doigt pendant son déplacement sur l’écran (touch.x ;touch.y).

Pour cela :

  • On teste si le doigt se déplace dans le tiers le plus bas de l’écran
  • Si c’est le cas, on recalcule la position centrale du Widget "Panier" en fonction de l’abscisse du doigt :

Mais cela ne va pas suffire !! le widget se déplace bien mais pas son image !!

Testez le code suivant pour vous en rendre compte. On print les différentes positions du centre du Widget "Panier". Essayez de faire glisser le panier et observer les positions :

Pour déplacer l’image avec le Widget, il faut détecter les changements de position du Widget pour recalculer aussitôt les positions de l’image.
On ajoute donc une méthode update_canvas() au Panier qui va être appelée à chaque changement de position grâce à un bind() :

Voici donc où en est votre code :

(Avez-vous remarqué que le panier peut se téléporter ? Est-ce grave ? Perso, cela ne me dérange pas... La correction de ce "problème" est laissée en exercice.

As-tu compris ?

  • Fais en sorte que le panier suive ton doigt (abscisse et ordonnée)
  • Ajoute un deuxième panier sur le tiers haut de l’écran et déplace les deux paniers horizontalement avec ton doigt.
  • Fais en sorte que le panier ne se téléporte plus. (Il faut tester si le point (x ;y) du doigt en mouvement sur l’écran est bien sur le panier...)

Page suivante : Création d’un lapin.

++++

Création d’un lapin

Les lapins du jeu

Changez le nom de l’image pour la faire correspondre avec votre code.

Pour commencer, on va créer une class "Lapin" de la même manière que le panier :

  • Un Widget pour le contenant
  • Une vélocité (rapidité) de chute dy. (Correspond au décalage vertical à chaque tour de boucle.)
  • Une taille fixe
  • Une position aléatoire (à 100 pixels sous le haut de l’écran pour commencer, ne pas oublier d’importer la librairie pour le random)
  • Une image taillée dans son canvas
  • Une détection des movements du Widget pour mettre à jour la position de l’image en cas de mouvement.

On importe la librairie pour le random et on créé un lapin dans le corps du Jeu...

Voici le résultat global :

Création d’un lapin

Le code :

As-tu compris ?

  • Place un autre lapin dans l’écran
  • Fais en sorte que le lapin soit positionné à une hauteur aléatoire

Page suivante : Faire chuter le lapin.

++++

Faire tomber le lapin

Pour faire tomber le lapin, on lui ajoute une méthode move(). Cette méthode sera, par la suite, appelée par une horloge. A chaque appel, la méthode move() va :

  • Recalculer la position du lapin
  • Tester si le lapin touche le sol pour le faire repartir d’en haut :

Dans le jeu, on créé une méthode update_chute(dt) qui sera appelée par l’horloge.
Cette méthode va appeler la méthode move() du lapin. (Par la suite, elle appellera le move() de chaque lapin et testera les collisions avec le panier.)

Ensuite, on fait partir l’horloge à la fin de la construction du Jeu tous 4 centièmes de seconde :

Remarques :

  • On pouvait aussi ajouter une horloge dans la classe lapin. Ainsi, chaque lapin aurait eu son horloge de chute.
  • En fonction du support (tablette, smarphone, PC...) il faudra peut-être modifier les paramètres de vitesse du jeu (horloge ou décalage vertical des lapins...)

Le code global : (Sans oublier d’importer l’horloge : Clock)

As-tu compris ?

  • Accélère ou ralentis la chute du lapin.
  • Ajoute un deuxième lapin et organise sa chute.
  • Replace le lapin sur une hauteur aléatoire (bien au dessus de l’écran) lorsqu’il a franchi le sol.

Page suivante : Multiplier les lapins.

++++

Multiplier les lapins

Pour créer plusieurs lapins sans saturer le jeu, on va utiliser une liste de lapins dans le corps du jeu : self.lapins=[] (initialisée à une liste vide). Cette liste sera créée avec 5 lapins au départ. Pour augmenter la difficulté du jeu, on pourra augmenter le nombre de lapins.

On pourrait penser que Kivy va charger 5 fois l’image du lapin (car on créé 5 lapins) mais ce n’est pas le cas. Kivy charge une seule fois l’image en cache puis réutilise cette ressource. Observer le shell pour vous en rendre compte.

Dans le corps du jeu :

Pour la chute de tous les lapins, on modifie la méthode update_chute(dt) en appelant pour chaque lapin de la liste sa fonction move() :

Ce qui donne :

As-tu compris ?

  • Augmente le nombre de lapins.

Difficile :

  • Ajoute un paramètre dans la classe "Lapin" afin du pouvoir définir aléatoirement son décalage vertical lors de la création. (Ainis, certains lapins iront plus vite que d’autres...)
  • Ajoute un autre animal en chute. (Soit on créé une nouvelle classe ressemblant à celle du lapin, soit on améliore la classe du lapin en lui ajoutant un autre paramètre comme le nom d’une image par exemple...)

Page suivante : Gérer les collisions entre le panier et les lapins.

++++

Collisions panier lapins

Encore une fois, il existe une une méthode toute faite pour tester les collisions entre Widget : collide_widget(Widget). Elle renvoie "True" ou "False".
Pour tester la collision entre les lapins et le panier, il suffit d’insérer ce test dans la fonction update_chute(dt) gérée par l’horloge :

On affiche donc "YES" à chaque collision. Mais vous l’aurez compris, afficher "YES" ne suffit pas et de plus, cet affichage s’opère tant que la collision est active, autrement dit, plusieurs fois tant que le lapin est en contact avec le panier.

Une nouvelle image pour le lapin :

YES du lapin capturé

Pour remédier à cela, on va ajouter une fonction prise() dans chaque lapin pour réagir à la première collision et opérer en fonction (une petite animation, replacer le lapin en haut pour une nouvelle chute...) :

Nous l’avons déjàdit mais Kivy charge une seule fois l’image en cache puis réutilise cette ressource. Cela permet de gagner en efficacité.

Il reste à appeler cette méthode lors d’une collision dans la fonction update_chute(dt) :

A cette étape, si vous lancez le programme, les lapins capturés se transforment en "YES" mais ils ne sont pas renvoyés en haut. Après 5 captures, il n’y a donc plus de lapins.

Lapins transformés en YES

Il faut donc :

  • laisser l’image "YES" pendant 0.5 seconde (le temps de l’observer)
  • placer le lapin sous le sol pour qu’il remonte avec la fonction move()
  • relancer le décalage vertical pour une nouvelle chute dy=-10

Pour cela, on ajoute une fonction prise_fin(dt) à chaque lapin que l’on appelera 0.5 seconde après le changement en "YES" :

On utilise à nouveau une horloge pour appeler cette méthode 0.5 à la fin de la fonction prise() :

Voici où en est notre code :

As-tu compris ?

  • Lors de la capture, tu peux faire bouger le "YES" vers le haut pour améliorer l’animation de la prise
  • Dans la méthode prise_fin(dt), au lieu de placer le lapin sous le sol après la capture, tu peux l’envoyer directement en haut avec une position aléatoire.
  • Ajoute une variable "score" dans le Jeu pour compter les points à chaque capture. (Tu peux l’afficher avec un print pour l’instant.)

A partir de là, le principe du jeu est terminé. Il reste toujours à cader une fin (victoire ou défaite).

Le reste est pour donner un peu plus de vie au jeu.

Page suivante : Créer un label pour le score.

++++

Un label pour le score

Nous allons créer une variable "score" à afficher dans un "Label". Ces deux élèments seront attachés à la classe "Jeu" lors de sa création (dans la méthode debut().

Voici les différentes étapes :

0) On importe la librairie pour le label :

1) On créer une variable "score" attachée au Jeu : (C’est un nombre entier integer)

2) On créer le label attaché au Jeu avec un texte composé de deux textes (string) "Score :" et "str(self.score). Ce dernier doit être transformé en string pour pourvoir l’ajouter au texte du label.

Le "markup" permet de gérer le texte du label (couleurs, taille...). On ne va pas s’attarder dessus mais il faut l’activer pour ne pas avoir de conflits avec notre canvas.
(A la fin de cette section, passez-le à "False" et vous verrez des lapins tout noirs...)

Attention !! Il faut insérer le label après l’image de fond du jeu. Sinon, le label sera derrière l’image de fond et vous ne le verrez pas !

3) On va ensuite gérer la taille de la police, réduire le label à son texte, le positionner puis changer sa couleur (en noir) :

4) Enfin, on l’ajoute dans l’ecran du Jeu (dans notre FloatLayout) :

A ce stade, le label s’affiche mais le score n’augmente pas...
A chaque capture, il faut donc augmenter le score et mettre à jour le texte du label.
On complète donc la fonction update_chute(dt) dans le test de collision :

Voici où en est votre code :

As-tu compris ?

  • Ajoute un label pour afficher le nombre de lapins non capturés.

Page suivante : Créer une animation au lancement du jeu.

++++

Lancer une animation

Voici un lien vers un exemple d’animation (il s’agit d’un bouton qui change de position et de taille de façon fluide : https://kivy.org/docs/examples

Le principe :

Le Widget à animer possède une position et une taille au départ. On renseigne dans l’animation une position et une taille finale (ou seulement l’une des deux) avec une durée et Kivy se charge de modifier le Widget de façon fluide.

Nous allons créer un compte à rebours au départ du jeu. "3", "2", "1" !

Pour cela :

0) Téléchargez les images :

Le chiffre trois
Le chiffre deux
Le chiffre un

1) Importez la librairie pour les animations :

2) Dans "Jeu", on créé un compteur d’animation initialisé à 3 puis on ajoute l’image ’3.png’ avec une taille nulle et une position centrée dans l’écran du jeu :

Evidemment, on ne voit pas le chiffre car il a une taille nulle...

3) On créer une fonction pour l’animation animation_start() dans le Jeu qui va produire deux animations succéssives :

Au départ, l’image est positionnée au centre de l’écran et a une taille nulle. La première animation va donc à la fois, faire grossir la taille de l’image pour atteindre la taille de l’écran mais aussi modifier la position de l’image pour atteindre la position (0,0). Ainsi, le chiffre 3 va grossir depuis le centre jusqu’à recouvrir l’écran. Elle dure 0.8 seconde : (Le paramètre "t" sert à définir la courbe de vitesse de l’animation)

On ajoute à la suite une deuxième animation à l’inverse (pour réduire le chiffre) :

On lance l’animation sur l’image :

Ce qui donne pour l’instant :

4) Pour l’instant, seul le chiffre 3 est animé. Il faut donc enchaîner l’animation sur les deux autres chiffres puis supprimer l’image du chiffre de l’écran. Pour cela, on créé une fonction intermédiaire : animation_next(animation,widget) qui sera appelée à la fin de chaque animation grâce à un événement : on_complete.

Notre fonction intermédiaire doit donc :

  • Enlever 1 au compteur d’animation
  • Supprimer l’image de l’écran lorsque le compte à rebours est terminé (if self.compteur_anim==0 :)
  • Sinon, charger l’image suivante et relancer l’animation

Ce qui donne :

Et pour la fonction animation_start() :

Au final, votre code ressemble à cela :

Il reste à placer les lapins plus haut (largement au dessus de l’écran) lors de leur création pour laisser du temps à l’animation. Ceci est laissé en exercice.

Votre jeu fonctionne mais il vous reste à coder une fin (victoire ou défaite). (En exercice aussi)

Vous pourrez ensuite, si vous le souhaitez, ajouter des sons ou encore créer un menu.

As-tu compris ?

  • Change la façon d’animer le compte à rebours
  • Place les lapins plus haut (largement au dessus de l’écran) lors de leur création pour laisser du temps à l’animation
  • Ajoute une image du style "GO" en quatrième image du compte à rebours. (Le plus simple étant d’appeler cette image "0.png")
  • Anime le "YES" lors de la capture des lapins (Fais-le partir d’une taille nulle pour lui faire reprendre sa taille normale)
  • Code une fin (victoire ou défaite).

Page suivante : Ajouter des sons dans le jeu.

++++

Ajouter des sons

Pour ajouter des, Kivy possède un SoundLoader :

Dans le jeu, nous alonns créer un son qui sera joué à chaque capture de lapin.
Afin d’éviter certains problèmes, le son sera en .ogg

Téléchagez le son :

Son YES.ogg

Ensuite, dans le corps du jeu :

  • On va charger un son :
  • Dans la fonction update_chute(self,dt), on joue le son à chaque fois qu’une collision est détectée avec la méthode play() :

Ce qui donne :

Tu peux maintenant ajouter des sons. Ce jeu est fini, il reste à coder la fin (victoire ou défaite) et à ajouter un écran de menu si tu veux...

Page suivante : Créer un écran de menu, gérer plusieurs écrans.

++++

Créer un menu au départ

Cette partie est plus complexe. Votre jeu fonctionne mais il vous reste à coder une fin (victoire ou défaite).

L’idée principale est de gérer plusieurs écrans. Pour cela, Kivy possède un ScreenManager.

Le principe :

On ajoute des écrans par leur nom dans le ScreenManager après les avoir déclarés ou au fur et à mesure. On indique ensuite au ScreenManager quel écran doit être affiché.

Nous allons donc créer deux écrans :

  • un écran pour le menu avec un nom "Menu"
  • un écran pour le jeu avec le nom "Game"
  • chaque écran aura une méthode build() permettant de lui donner un nom et aussi de définir ce qu’il contient (Image de fond, Layout, Boutons...) :

Tout d’abord, il faut importer les librairies pour les écrans, les boutons et un BoxLayout pour la disposition de nos boutons dans l’écran "Menu" :

Ensuite, on créé les deux écrans :

Pour l’instant, ces deux écrans ne servent à rien.
Nous allons lancer dans un premier temps l’écran de "Menu" même s’il ne contient rien. Pour cela, on doit créer une variable globale sm=ScreenManager() puis modifier la fonction build() de notre application LapinApp :

Vous avez donc, pour l’instant, un écran noir et le jeu n’est pas lancé.

Construisons l’écran "Menu". Dans son build() :

  • On ajoute une image de fond
  • On créé une BoxLayout avec une orientation verticale, une marge intérieure de 100 pixels et un espacement entre les Widgets quelle contient de 10 pixels
  • On créé deux boutons, un qui envoie vers une fonction Vers_Game() pour, plus tard, construire et afficher l’écran du jeu et un autre que ne fait rien, juste pour voir....
  • On ajoute ces deux boutons dans la BoxLayout
  • On ajoute la BoxLayout dans l’écran.

On ajoute aussi donc deux fonctions à notre écran "Menu" associées à nos deux boutons (Elles ne font rien pour l’instant) :

Voici l’image de fond :

Fond du menu

L’écran du Menu :

Image de l’écran du menu

Il reste donc a activer correctement le bouton "Jouer" afin de :

  • Créer un écran de jeu
  • Construire l’écran du jeu
  • Ajouter l’écran dans le ScreenManager()
  • Afficher l’écran du jeu :

A ce stade, le bouton "Jouer" envoie bien vers l’écran "Game" mais ce dernier ne contient rien... Il faut lui insérer le jeu construit :

Voici au final votre code :

As-tu compris ?

  • Complète l’écran "Menu" avec ce que tu veux (Boutons pour choisir la difficulté, Animations en fond...)
  • Ajoute un troisième écran (en cas de défaite par exemple...)