Apprends à créer un jeu graphique (Kivy)

(actualisé le ) par wlaidet

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 :

from kivy.app import App
from kivy.core.window import Window
from kivy.core.window import WindowBase
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import Image

class Jeu(FloatLayout):
    def debut(self):
        #On recupere la taille de l'ecran:
        self.size=Window.size
        #Une image de fond:
        self.add_widget(Image(source='fond1.jpg',allow_stretch=True,keep_ratio=False))

class LapinsApp(App):
    def build(self):
        New_game=Jeu()#Creation du jeu
        New_game.debut()#Initialisation du jeu
        return New_game#Envoie de l'affichage

if __name__ == '__main__':
    LapinsApp().run()

Télécharger

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) :

from kivy.graphics import Rectangle
from kivy.uix.widget import Widget

Télécharger

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
class Panier(Widget):
    def __init__(self,canvas):
        self.canvas=canvas
        #Taille et position:
        self.size=(Window.size[0]*0.1,Window.size[1]*0.1)
        self.pos=(0,Window.size[1]*0.02)
        #Ajout de l'image (add_wiget fonctionne aussi):
        with self.canvas:
            self.dessin = Rectangle(source='panier.png',size=self.size, pos=self.pos)

Télécharger

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 :

from kivy.app import App
from kivy.core.window import Window
from kivy.core.window import WindowBase
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import Image
from kivy.graphics import Rectangle
from kivy.uix.widget import Widget

class Panier(Widget):
    def __init__(self,canvas):
        self.canvas=canvas
        #Taille et position:
        self.size=(Window.size[0]*0.1,Window.size[1]*0.1)
        self.pos=(0,Window.size[1]*0.02)
        #Ajout de l'image (add_wiget fonctionne aussi):
        with self.canvas:
            self.dessin = Rectangle(source='panier.png',size=self.size, pos=self.pos)

class Jeu(FloatLayout):
    def debut(self):
        #On recupere la taille de l'ecran:
        self.size=Window.size
        #Une image de fond:
        self.add_widget(Image(source='fond1.jpg',allow_stretch=True,keep_ratio=False))
        #Creation du panier:
        self.panier=Panier(self.canvas)

class LapinsApp(App):
    def build(self):
        New_game=Jeu()#Creation du jeu
        New_game.debut()#Initialisation du jeu
        return New_game#Envoie de l'affichage

if __name__ == '__main__':
    LapinsApp().run()

Télécharger

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 :

#Creation du panier:
self.panier=Image(source='panier.png',allow_stretch=True,keep_ratio=False)
self.panier.pos=(0,0)
self.panier.size_hint=(None,None)
self.panier.size=(Window.size[0]*0.1,Window.size[1]*0.1)
self.add_widget(self.panier)

Télécharger

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 :
def on_touch_move(self,touch):#Deplacement du panier
    #Si on touche le tiers du bas
    if touch.y<self.size[1]/3:
        #On deplace le panier
        self.panier.center_x=touch.x

Télécharger

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 :

from kivy.app import App
from kivy.core.window import Window
from kivy.core.window import WindowBase
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import Image
from kivy.graphics import Rectangle
from kivy.uix.widget import Widget
 
class Panier(Widget):
    def __init__(self,canvas):
        self.canvas=canvas
        #Taille et position:
        self.size=(Window.size[0]*0.1,Window.size[1]*0.1)
        self.pos=(0,Window.size[1]*0.02)
        #Ajout de l'image (add_wiget fonctionne aussi):
        with self.canvas:
            self.dessin = Rectangle(source='panier.png',size=self.size, pos=self.pos)
 
class Jeu(FloatLayout):
    def debut(self):
        #On recupere la taille de l'ecran:
        self.size=Window.size
        #Une image de fond:
        self.add_widget(Image(source='fond1.jpg',allow_stretch=True,keep_ratio=False))
        #Creation du panier:
        self.panier=Panier(self.canvas)
 
    def on_touch_move(self,touch):#Deplacement du panier
        #Si on touche le tiers du bas
        if touch.y<self.size[1]/3:
            #On deplace le panier
            self.panier.center_x=touch.x
            print(self.panier.center)
 
class LapinsApp(App):
    def build(self):
        New_game=Jeu()#Creation du jeu
        New_game.debut()#Initialisation du jeu
        return New_game#Envoie de l'affichage
 
if __name__ == '__main__':
    LapinsApp().run()

Télécharger

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() :

class Panier(Widget):
    def __init__(self,canvas):
        self.canvas=canvas
        #Taille et position:
        self.size=(Window.size[0]*0.1,Window.size[1]*0.1)
        self.pos=(0,Window.size[1]*0.02)
        #Ajout de l'image (add_wiget fonctionne aussi):
        with self.canvas:
            self.dessin = Rectangle(source='panier.png',size=self.size, pos=self.pos)
        #On associe le mouvement du panier et son image:
        self.bind(pos=self.update_canvas)
 
    def update_canvas(self, *args):#Mise a jour des positions de l'image:
        self.dessin.pos = self.pos

Télécharger

Voici donc où en est votre code :

from kivy.app import App
from kivy.core.window import Window
from kivy.core.window import WindowBase
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import Image
from kivy.graphics import Rectangle
from kivy.uix.widget import Widget

class Panier(Widget):
    def __init__(self,canvas):
        self.canvas=canvas
        #Taille et position:
        self.size=(Window.size[0]*0.1,Window.size[1]*0.1)
        self.pos=(0,Window.size[1]*0.02)
        #Ajout de l'image (add_wiget fonctionne aussi):
        with self.canvas:
            self.dessin = Rectangle(source='panier.png',size=self.size, pos=self.pos)
        #On associe le mouvement du panier et son image:
        self.bind(pos=self.update_canvas)
       
    def update_canvas(self, *args):#Mise a jour des positions de l'image:
        self.dessin.pos = self.pos

class Jeu(FloatLayout):
    def debut(self):
        #On recupere la taille de l'ecran:
        self.size=Window.size
        #Une image de fond:
        self.add_widget(Image(source='fond1.jpg',allow_stretch=True,keep_ratio=False))
        #Creation du panier:
        self.panier=Panier(self.canvas)
   
    def on_touch_move(self,touch):#Deplacement du panier
        if touch.y<self.size[1]/3:
            self.panier.center_x=touch.x

class LapinsApp(App):
    def build(self):
        New_game=Jeu()#Creation du jeu
        New_game.debut()#Initialisation du jeu
        return New_game#Envoie de l'affichage

if __name__ == '__main__':
    LapinsApp().run()

Télécharger

(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.
class Lapin(Widget):
    def __init__(self,canvas):
        self.dy=-10
        self.canvas=canvas
        #Taille et position aleatoire:
        self.size=(Window.size[0]*0.05,Window.size[1]*0.1)
        self.x = random.randint(0,int(Window.size[0]-self.size[0]))
        self.y=Window.size[1]-100
        #Ajout de l'image du lapin:
        with self.canvas:
            self.dessin = Rectangle(source='lapin.png',size=self.size, pos=self.pos)
        #Detection des mouvements:
        self.bind(pos=self.update_canvas)

    def update_canvas(self, *args):#Mise a jour des positions de l'image:
        self.dessin.pos = self.pos

Télécharger

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 :

from kivy.app import App
from kivy.core.window import Window
from kivy.core.window import WindowBase
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import Image
from kivy.graphics import Rectangle
from kivy.uix.widget import Widget

#Nombres aleatoires:
import random

class Lapin(Widget):
    def __init__(self,canvas):
        self.dy=-10
        self.canvas=canvas
        #Taille et position aleatoire:
        self.size=(Window.size[0]*0.05,Window.size[1]*0.1)
        self.x = random.randint(0,int(Window.size[0]-self.size[0]))
        self.y=Window.size[1]-100
        #Ajout de l'image du lapin:
        with self.canvas:
            self.dessin = Rectangle(source='lapin.png',size=self.size, pos=self.pos)
        #Detection des mouvements:
        self.bind(pos=self.update_canvas)
 
    def update_canvas(self, *args):#Mise a jour des positions de l'image:
        self.dessin.pos = self.pos

class Panier(Widget):
    def __init__(self,canvas):
        self.canvas=canvas
        #Taille et position:
        self.size=(Window.size[0]*0.1,Window.size[1]*0.1)
        self.pos=(0,Window.size[1]*0.02)
        #Ajout de l'image (add_wiget fonctionne aussi):
        with self.canvas:
            self.dessin = Rectangle(source='panier.png',size=self.size, pos=self.pos)
        #On associe le mouvement du panier et son image:
        self.bind(pos=self.update_canvas)
 
    def update_canvas(self, *args):#Mise a jour des positions de l'image:
        self.dessin.pos = self.pos
 
class Jeu(FloatLayout):
    def debut(self):
        #On recupere la taille de l'ecran:
        self.size=Window.size
        #Une image de fond:
        self.add_widget(Image(source='fond1.jpg',allow_stretch=True,keep_ratio=False))
        #Creation du panier:
        self.panier=Panier(self.canvas)
        #Creation d'un lapin:
        self.lapin=Lapin(self.canvas)
 
    def on_touch_move(self,touch):#Deplacement du panier
        if touch.y<self.size[1]/3:
            self.panier.center_x=touch.x
 
class LapinsApp(App):
    def build(self):
        New_game=Jeu()#Creation du jeu
        New_game.debut()#Initialisation du jeu
        return New_game#Envoie de l'affichage
 
if __name__ == '__main__':
    LapinsApp().run()

Télécharger

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 :
def move(self):
    #On recalcule les positions:
    self.y=self.y+self.dy
    #On teste la fin de la chute:
    if self.y<=0-self.size[1]:
        #Repositionnement aleatoire en haut:
        self.y=Window.size[1]
        self.x=random.randint(0,int(Window.size[0]-self.size[0]))

Télécharger

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.)

    def update_chute(self,dt):#Chute des lapins et tests de collisions
        self.lapin.move()

Télécharger

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

Clock.schedule_interval(self.update_chute, 4.0/100.0)

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)

from kivy.app import App
from kivy.core.window import Window
from kivy.core.window import WindowBase
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import Image
from kivy.graphics import Rectangle
from kivy.uix.widget import Widget
from kivy.clock import Clock

#Nombres aleatoires:
import random

class Lapin(Widget):
    def __init__(self,canvas):
        self.dy=-10
        self.canvas=canvas
        #Taille et position aleatoire:
        self.size=(Window.size[0]*0.05,Window.size[1]*0.1)
        self.x = random.randint(0,int(Window.size[0]-self.size[0]))
        self.y=Window.size[1]-100
        #Ajout de l'image du lapin:
        with self.canvas:
            self.dessin = Rectangle(source='lapin.png',size=self.size, pos=self.pos)
        #Detection des mouvements:
        self.bind(pos=self.update_canvas)
 
    def update_canvas(self, *args):#Mise a jour des positions de l'image:
        self.dessin.pos = self.pos
   
    def move(self):
        #On recalcule les positions:
        self.y=self.y+self.dy
        #On teste la fin de la chute:
        if self.y<=0-self.size[1]:
            #Repositionnement aleatoire en haut:
            self.y=Window.size[1]
            self.x=random.randint(0,int(Window.size[0]-self.size[0]))

class Panier(Widget):
    def __init__(self,canvas):
        self.canvas=canvas
        #Taille et position:
        self.size=(Window.size[0]*0.1,Window.size[1]*0.1)
        self.pos=(0,Window.size[1]*0.02)
        #Ajout de l'image (add_wiget fonctionne aussi):
        with self.canvas:
            self.dessin = Rectangle(source='panier.png',size=self.size, pos=self.pos)
        #On associe le mouvement du panier et son image:
        self.bind(pos=self.update_canvas)
 
    def update_canvas(self, *args):#Mise a jour des positions de l'image:
        self.dessin.pos = self.pos
 
class Jeu(FloatLayout):
    def debut(self):
        #On recupere la taille de l'ecran:
        self.size=Window.size
        #Une image de fond:
        self.add_widget(Image(source='fond1.jpg',allow_stretch=True,keep_ratio=False))
        #Creation du panier:
        self.panier=Panier(self.canvas)
        #Creation d'un lapin:
        self.lapin=Lapin(self.canvas)
       
        #Depart de l'horloge du jeu:
        Clock.schedule_interval(self.update_chute, 4.0/100.0)

    def update_chute(self,dt):#Chute des lapins et tests de collisions
        self.lapin.move()
 
    def on_touch_move(self,touch):#Deplacement du panier
        if touch.y<self.size[1]/3:
            self.panier.center_x=touch.x
 
class LapinsApp(App):
    def build(self):
        New_game=Jeu()#Creation du jeu
        New_game.debut()#Initialisation du jeu
        return New_game#Envoie de l'affichage
 
if __name__ == '__main__':
    LapinsApp().run()

Télécharger

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 :

#Creation des lapins:
self.lapins=[]
for i in range(0,5):#On ajoute les lapins
    self.lapins.append(Lapin(self.canvas))

Télécharger

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() :

def update_chute(self,dt):#Chute des lapins et tests de collisions
    for lapin in self.lapins:
        lapin.move()

Télécharger

Ce qui donne :

from kivy.app import App
from kivy.core.window import Window
from kivy.core.window import WindowBase
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import Image
from kivy.graphics import Rectangle
from kivy.uix.widget import Widget
from kivy.clock import Clock
 
#Nombres aleatoires:
import random
 
class Lapin(Widget):
    def __init__(self,canvas):
        self.dy=-10
        self.canvas=canvas
        #Taille et position aleatoire:
        self.size=(Window.size[0]*0.05,Window.size[1]*0.1)
        self.x = random.randint(0,int(Window.size[0]-self.size[0]))
        self.y=Window.size[1]-100
        #Ajout de l'image du lapin:
        with self.canvas:
            self.dessin = Rectangle(source='lapin.png',size=self.size, pos=self.pos)
        #Detection des mouvements:
        self.bind(pos=self.update_canvas)
 
    def update_canvas(self, *args):#Mise a jour des positions de l'image:
        self.dessin.pos = self.pos
 
    def move(self):
        #On recalcule les positions:
        self.y=self.y+self.dy
        #On teste la fin de la chute:
        if self.y<=0-self.size[1]:
            #Repositionnement aleatoire en haut:
            self.y=Window.size[1]
            self.x=random.randint(0,int(Window.size[0]-self.size[0]))
 
class Panier(Widget):
    def __init__(self,canvas):
        self.canvas=canvas
        #Taille et position:
        self.size=(Window.size[0]*0.1,Window.size[1]*0.1)
        self.pos=(0,Window.size[1]*0.02)
        #Ajout de l'image (add_wiget fonctionne aussi):
        with self.canvas:
            self.dessin = Rectangle(source='panier.png',size=self.size, pos=self.pos)
        #On associe le mouvement du panier et son image:
        self.bind(pos=self.update_canvas)
 
    def update_canvas(self, *args):#Mise a jour des positions de l'image:
        self.dessin.pos = self.pos
 
class Jeu(FloatLayout):
    def debut(self):
        #On recupere la taille de l'ecran:
        self.size=Window.size
        #Une image de fond:
        self.add_widget(Image(source='fond1.jpg',allow_stretch=True,keep_ratio=False))
        #Creation du panier:
        self.panier=Panier(self.canvas)
        #Creation des lapins:
        self.lapins=[]
        for i in range(0,5):#On ajoute les lapins
            self.lapins.append(Lapin(self.canvas))
           
        #Depart de l'horloge du jeu:
        Clock.schedule_interval(self.update_chute, 4.0/100.0)
 
    def update_chute(self,dt):#Chute des lapins et tests de collisions
        for lapin in self.lapins:
            lapin.move()
 
    def on_touch_move(self,touch):#Deplacement du panier
        if touch.y<self.size[1]/3:
            self.panier.center_x=touch.x
 
class LapinsApp(App):
    def build(self):
        New_game=Jeu()#Creation du jeu
        New_game.debut()#Initialisation du jeu
        return New_game#Envoie de l'affichage
 
if __name__ == '__main__':
    LapinsApp().run()

Télécharger

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 :

def update_chute(self,dt):#Chute des lapins et tests de collisions
    for lapin in self.lapins:
        lapin.move()
        if lapin.collide_widget(self.panier):
                print('YES')

Télécharger

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...) :

def prise(self):#Changement d'image pour le succes:
    self.dy=0#On stoppe la chute
    #Position au dessus du panier pour stopper la collision:
    self.y=Window.size[1]*0.2  
    self.dessin.source='YES.png'#Nouvelle image

Télécharger

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) :

def update_chute(self,dt):#Chute des lapins et tests de collisions
    for lapin in self.lapins:
        lapin.move()
        if lapin.collide_widget(self.panier):
                lapin.prise()#Animation de la capture

Télécharger

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" :

def prise_fin(self,dt):#Retour a l'image de lapin et en haut:
    self.y=0-self.size[1]#On le place en dessous pour qu'il remonte
    self.dessin.source='lapin.png'    #On change l'image
    self.dy=-10    #On relance la chute

Télécharger

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

def prise(self):#Changement d'image pour le succes:
    self.dy=0#On stoppe la chute
    #Position au dessus du panier pour stopper la collision:
    self.y=Window.size[1]*0.2  
    self.dessin.source='YES.png'#Nouvelle image
    #On lance le nouveau lapin dans 0.5 seconde:
    Clock.schedule_once(self.prise_fin, 0.5)

Télécharger

Voici où en est notre code :

from kivy.app import App
from kivy.core.window import Window
from kivy.core.window import WindowBase
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import Image
from kivy.graphics import Rectangle
from kivy.uix.widget import Widget
from kivy.clock import Clock
 
#Nombres aleatoires:
import random
 
class Lapin(Widget):
    def __init__(self,canvas):
        self.dy=-10
        self.canvas=canvas
        #Taille et position aleatoire:
        self.size=(Window.size[0]*0.05,Window.size[1]*0.1)
        self.x = random.randint(0,int(Window.size[0]-self.size[0]))
        self.y=Window.size[1]-100
        #Ajout de l'image du lapin:
        with self.canvas:
            self.dessin = Rectangle(source='lapin.png',size=self.size, pos=self.pos)
        #Detection des mouvements:
        self.bind(pos=self.update_canvas)
 
    def update_canvas(self, *args):#Mise a jour des positions de l'image:
        self.dessin.pos = self.pos
 
    def move(self):
        #On recalcule les positions:
        self.y=self.y+self.dy
        #On teste la fin de la chute:
        if self.y<=0-self.size[1]:
            #Repositionnement aleatoire en haut:
            self.y=Window.size[1]
            self.x=random.randint(0,int(Window.size[0]-self.size[0]))
   
    def prise(self):#Changement d'image pour le succes:
        self.dy=0#On stoppe la chute
        #Position au dessus du panier pour stopper la collision:
        self.y=Window.size[1]*0.2  
        self.dessin.source='YES.png'#Nouvelle image
        #On lance le nouveau lapin dans 0.5 seconde:
        Clock.schedule_once(self.prise_fin, 0.5)
   
    def prise_fin(self,dt):#Retour a l'image de lapin et en haut:
        self.y=0-self.size[1]#On le place en dessous pour qu'il remonte
        self.dessin.source='lapin.png'   #On change l'image
        self.dy=-10    #On relance la chute
 
class Panier(Widget):
    def __init__(self,canvas):
        self.canvas=canvas
        #Taille et position:
        self.size=(Window.size[0]*0.1,Window.size[1]*0.1)
        self.pos=(0,Window.size[1]*0.02)
        #Ajout de l'image (add_wiget fonctionne aussi):
        with self.canvas:
            self.dessin = Rectangle(source='panier.png',size=self.size, pos=self.pos)
        #On associe le mouvement du panier et son image:
        self.bind(pos=self.update_canvas)
 
    def update_canvas(self, *args):#Mise a jour des positions de l'image:
        self.dessin.pos = self.pos
 
class Jeu(FloatLayout):
    def debut(self):
        #On recupere la taille de l'ecran:
        self.size=Window.size
        #Une image de fond:
        self.add_widget(Image(source='fond1.jpg',allow_stretch=True,keep_ratio=False))
        #Creation du panier:
        self.panier=Panier(self.canvas)
        #Creation des lapins:
        self.lapins=[]
        for i in range(0,5):#On ajoute les lapins
            self.lapins.append(Lapin(self.canvas))
           
        #Depart de l'horloge du jeu:
        Clock.schedule_interval(self.update_chute, 4.0/100.0)
 
    def update_chute(self,dt):#Chute des lapins et tests de collisions
        for lapin in self.lapins:
            lapin.move()
            if lapin.collide_widget(self.panier):
                    lapin.prise()#Animation de la capture
 
    def on_touch_move(self,touch):#Deplacement du panier
        if touch.y<self.size[1]/3:
            self.panier.center_x=touch.x
 
class LapinsApp(App):
    def build(self):
        New_game=Jeu()#Creation du jeu
        New_game.debut()#Initialisation du jeu
        return New_game#Envoie de l'affichage
 
if __name__ == '__main__':
    LapinsApp().run()

Télécharger

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 :

from kivy.uix.label import Label

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

self.score=0

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 !

self.label=Label(text='Scrore : '+str(self.score),markup=True)

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) :

#Taille de la police en fonction de l'ecran:
self.label.font_size=self.size[0]*0.05
#Le label ne doit pas ecraser tout l'ecran:
self.label.size_hint=(None,None)
#Position du label vers le centre de l'ecran:
self.label.pos=(Window.size[0]*0.47,Window.size[1]*0.45)
self.label.color=[0,0,0,1]

Télécharger

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

#On ajoute le label dans l'ecran du jeu:
self.add_widget(self.label)

Télécharger

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 :

def update_chute(self,dt):#Chute des lapins et tests de collisions
    for lapin in self.lapins:
        lapin.move()
        if lapin.collide_widget(self.panier):
                lapin.prise()#Animation de la capture
                self.score+=1
                self.label.text='Scrore : '+str(self.score)

Télécharger

Voici où en est votre code :

from kivy.app import App
from kivy.core.window import Window
from kivy.core.window import WindowBase
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import Image
from kivy.graphics import Rectangle
from kivy.uix.widget import Widget
from kivy.clock import Clock
from kivy.uix.label import Label
 
#Nombres aleatoires:
import random
 
class Lapin(Widget):
    def __init__(self,canvas):
        self.dy=-10
        self.canvas=canvas
        #Taille et position aleatoire:
        self.size=(Window.size[0]*0.05,Window.size[1]*0.1)
        self.x = random.randint(0,int(Window.size[0]-self.size[0]))
        self.y=Window.size[1]-100
        #Ajout de l'image du lapin:
        with self.canvas:
            self.dessin = Rectangle(source='lapin.png',size=self.size, pos=self.pos)
        #Detection des mouvements:
        self.bind(pos=self.update_canvas)
 
    def update_canvas(self, *args):#Mise a jour des positions de l'image:
        self.dessin.pos = self.pos
 
    def move(self):
        #On recalcule les positions:
        self.y=self.y+self.dy
        #On teste la fin de la chute:
        if self.y<=0-self.size[1]:
            #Repositionnement aleatoire en haut:
            self.y=Window.size[1]
            self.x=random.randint(0,int(Window.size[0]-self.size[0]))
   
    def prise(self):#Changement d'image pour le succes:
        self.dy=0#On stoppe la chute
        #Position au dessus du panier pour stopper la collision:
        self.y=Window.size[1]*0.2  
        self.dessin.source='YES.png'#Nouvelle image
        #On lance le nouveau lapin dans 0.5 seconde:
        Clock.schedule_once(self.prise_fin, 0.5)
   
    def prise_fin(self,dt):#Retour a l'image de lapin et en haut:
        self.y=0-self.size[1]#On le place en dessous pour qu'il remonte
        self.dessin.source='lapin.png'   #On change l'image
        self.dy=-10    #On relance la chute
 
class Panier(Widget):
    def __init__(self,canvas):
        self.canvas=canvas
        #Taille et position:
        self.size=(Window.size[0]*0.1,Window.size[1]*0.1)
        self.pos=(0,Window.size[1]*0.02)
        #Ajout de l'image (add_wiget fonctionne aussi):
        with self.canvas:
            self.dessin = Rectangle(source='panier.png',size=self.size, pos=self.pos)
        #On associe le mouvement du panier et son image:
        self.bind(pos=self.update_canvas)
 
    def update_canvas(self, *args):#Mise a jour des positions de l'image:
        self.dessin.pos = self.pos
 
class Jeu(FloatLayout):
    def debut(self):
        #On recupere la taille de l'ecran:
        self.size=Window.size
        #Une image de fond:
        self.add_widget(Image(source='fond1.jpg',allow_stretch=True,keep_ratio=False))
       
        #Un label pour le score:
        self.score=0#Creation de la variable score
        self.label=Label(text='Scrore : '+str(self.score),markup=True)
        #Taille de la police en fonction de l'ecran:
        self.label.font_size=self.size[0]*0.05
        #Le label ne doit pas ecraser tout l'ecran:
        self.label.size_hint=(None,None)
        #Position du label vers le centre de l'ecran:
        self.label.pos=(Window.size[0]*0.47,Window.size[1]*0.45)
        self.label.color=[0,0,0,1]
        #On ajoute le label dans l'ecran du jeu:
        self.add_widget(self.label)
       
        #Creation du panier:
        self.panier=Panier(self.canvas)
        #Creation des lapins:
        self.lapins=[]
        for i in range(0,5):#On ajoute les lapins
            self.lapins.append(Lapin(self.canvas))
           
        #Depart de l'horloge du jeu:
        Clock.schedule_interval(self.update_chute, 4.0/100.0)
 
    def update_chute(self,dt):#Chute des lapins et tests de collisions
        for lapin in self.lapins:
            lapin.move()
            if lapin.collide_widget(self.panier):
                    lapin.prise()#Animation de la capture
                    self.score+=1
                    self.label.text='Scrore : '+str(self.score)
 
    def on_touch_move(self,touch):#Deplacement du panier
        if touch.y<self.size[1]/3:
            self.panier.center_x=touch.x
 
class LapinsApp(App):
    def build(self):
        New_game=Jeu()#Creation du jeu
        New_game.debut()#Initialisation du jeu
        return New_game#Envoie de l'affichage
 
if __name__ == '__main__':
    LapinsApp().run()

Télécharger

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 :

from kivy.animation import Animation

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 :

#Creation des chiffres pour de depart:
self.compteur_anim=3
self.chiffre=Image(source='3.png',allow_stretch=True,keep_ratio=False)
self.chiffre.size_hint=(0,0)
self.chiffre.pos=self.center
self.add_widget(self.chiffre)

Télécharger

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)

anim = Animation(pos=(0,0),size=self.size,t='in_quad',duration=0.8)

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

anim += Animation(pos=self.center,size=(0,0),duration=0.8)

On lance l’animation sur l’image :

anim.start(self.chiffre)

Ce qui donne pour l’instant :

def animation_start(self):#Depart de l'animation start
    #On compose l'animation avec deux anim successives:
    anim = Animation(pos=self.pos,size=self.size,t='in_quad',duration=0.8)
    anim += Animation(pos=self.center,size=(0,0),duration=0.8)
    #Depart de l'animation:
    anim.start(self.chiffre)

Télécharger

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 :

def animation_next(self,animation,widget):#Animation suivante:
    self.compteur_anim-=1
    if self.compteur_anim==0:
        self.remove_widget(self.chiffre)
    else:
        self.chiffre.source=str(self.compteur_anim)+'.png'
        self.animation_start()  

Télécharger

Et pour la fonction animation_start() :

def animation_start(self):#Depart de l'animation start
    #On compose l'animation avec deux anim successives:
    anim = Animation(pos=(0,0),size=self.size,t='in_quad',duration=0.8)
    anim += Animation(pos=self.center,size=(0,0),duration=0.8)
    #On prevoie de lancer le prochain chiffre a la fin de l'animation:
    anim.bind(on_complete=self.animation_next)
    #Depart de l'animation:
    anim.start(self.chiffre)

Télécharger

Au final, votre code ressemble à cela :

from kivy.app import App
from kivy.core.window import Window
from kivy.core.window import WindowBase
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import Image
from kivy.graphics import Rectangle
from kivy.uix.widget import Widget
from kivy.clock import Clock
from kivy.uix.label import Label
from kivy.animation import Animation
#Nombres aleatoires:
import random
 
class Lapin(Widget):
    def __init__(self,canvas):
        self.dy=-10
        self.canvas=canvas
        #Taille et position aleatoire:
        self.size=(Window.size[0]*0.05,Window.size[1]*0.1)
        self.x = random.randint(0,int(Window.size[0]-self.size[0]))
        self.y=Window.size[1]-100
        #Ajout de l'image du lapin:
        with self.canvas:
            self.dessin = Rectangle(source='lapin.png',size=self.size, pos=self.pos)
        #Detection des mouvements:
        self.bind(pos=self.update_canvas)
 
    def update_canvas(self, *args):#Mise a jour des positions de l'image:
        self.dessin.pos = self.pos
 
    def move(self):
        #On recalcule les positions:
        self.y=self.y+self.dy
        #On teste la fin de la chute:
        if self.y<=0-self.size[1]:
            #Repositionnement aleatoire en haut:
            self.y=Window.size[1]
            self.x=random.randint(0,int(Window.size[0]-self.size[0]))
   
    def prise(self):#Changement d'image pour le succes:
        self.dy=0#On stoppe la chute
        #Position au dessus du panier pour stopper la collision:
        self.y=Window.size[1]*0.2  
        self.dessin.source='YES.png'#Nouvelle image
        #On lance le nouveau lapin dans 0.5 seconde:
        Clock.schedule_once(self.prise_fin, 0.5)
   
    def prise_fin(self,dt):#Retour a l'image de lapin et en haut:
        self.y=0-self.size[1]#On le place en dessous pour qu'il remonte
        self.dessin.source='lapin.png'   #On change l'image
        self.dy=-10    #On relance la chute
 
class Panier(Widget):
    def __init__(self,canvas):
        self.canvas=canvas
        #Taille et position:
        self.size=(Window.size[0]*0.1,Window.size[1]*0.1)
        self.pos=(0,Window.size[1]*0.02)
        #Ajout de l'image (add_wiget fonctionne aussi):
        with self.canvas:
            self.dessin = Rectangle(source='panier.png',size=self.size, pos=self.pos)
        #On associe le mouvement du panier et son image:
        self.bind(pos=self.update_canvas)
 
    def update_canvas(self, *args):#Mise a jour des positions de l'image:
        self.dessin.pos = self.pos
 
class Jeu(FloatLayout):
    def debut(self):
        #On recupere la taille de l'ecran:
        self.size=Window.size
        #Une image de fond:
        self.add_widget(Image(source='fond1.jpg',allow_stretch=True,keep_ratio=False))
       
        #Un label pour le score:
        self.score=0#Creation de la variable score
        self.label=Label(text='Scrore : '+str(self.score),markup=True)
        #Taille de la police en fonction de l'ecran:
        self.label.font_size=self.size[0]*0.05
        #Le label ne doit pas ecraser tout l'ecran:
        self.label.size_hint=(None,None)
        #Position du label vers le centre de l'ecran:
        self.label.pos=(Window.size[0]*0.47,Window.size[1]*0.45)
        self.label.color=[0,0,0,1]
        #On ajoute le label dans l'ecran du jeu:
        self.add_widget(self.label)
       
        #Creation du panier:
        self.panier=Panier(self.canvas)
        #Creation des lapins:
        self.lapins=[]
        for i in range(0,5):#On ajoute les lapins
            self.lapins.append(Lapin(self.canvas))
       
        #Creation des chiffres pour de depart:
        self.compteur_anim=3
        self.chiffre=Image(source='3.png',allow_stretch=True,keep_ratio=False)
        self.chiffre.size_hint=(0,0)
        self.chiffre.pos=self.center
        self.add_widget(self.chiffre)
        #Lancement de l'animation start:
        self.animation_start()
       
        #Depart de l'horloge du jeu:
        Clock.schedule_interval(self.update_chute, 4.0/100.0)
 
    def update_chute(self,dt):#Chute des lapins et tests de collisions
        for lapin in self.lapins:
            lapin.move()
            if lapin.collide_widget(self.panier):
                    lapin.prise()#Animation de la capture
                    self.score+=1
                    self.label.text='Scrore : '+str(self.score)
 
    def on_touch_move(self,touch):#Deplacement du panier
        if touch.y<self.size[1]/3:
            self.panier.center_x=touch.x
   
    def animation_start(self):#Depart de l'animation start
        #On compose l'animation avec deux anim successives:
        anim = Animation(pos=(0,0),size=self.size,t='in_quad',duration=0.8)
        anim += Animation(pos=self.center,size=(0,0),duration=0.8)
        #On prevoie de lancer le prochain chiffre a la fin de l'animation:
        anim.bind(on_complete=self.animation_next)
        #Depart de l'animation:
        anim.start(self.chiffre)
       
    def animation_next(self,animation,widget):#Animation suivante:
        self.compteur_anim-=1
        if self.compteur_anim==0:
            self.remove_widget(self.chiffre)
        else:
            self.chiffre.source=str(self.compteur_anim)+'.png'
            self.animation_start()  
 
class LapinsApp(App):
    def build(self):
        New_game=Jeu()#Creation du jeu
        New_game.debut()#Initialisation du jeu
        return New_game#Envoie de l'affichage
 
if __name__ == '__main__':
    LapinsApp().run()

Télécharger

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 :

from kivy.core.audio import 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 :
#On charge les sons :
self.son_yes = SoundLoader.load('YES.ogg')

Télécharger

  • 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() :
self.son_yes.play()

Ce qui donne :

from kivy.app import App
from kivy.core.window import Window
from kivy.core.window import WindowBase
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import Image
from kivy.graphics import Rectangle
from kivy.uix.widget import Widget
from kivy.clock import Clock
from kivy.uix.label import Label
from kivy.animation import Animation
from kivy.core.audio import SoundLoader
#Nombres aleatoires:
import random
 
class Lapin(Widget):
    def __init__(self,canvas):
        self.dy=-10
        self.canvas=canvas
        #Taille et position aleatoire:
        self.size=(Window.size[0]*0.05,Window.size[1]*0.1)
        self.x = random.randint(0,int(Window.size[0]-self.size[0]))
        self.y=Window.size[1]-100
        #Ajout de l'image du lapin:
        with self.canvas:
            self.dessin = Rectangle(source='lapin.png',size=self.size, pos=self.pos)
        #Detection des mouvements:
        self.bind(pos=self.update_canvas)
 
    def update_canvas(self, *args):#Mise a jour des positions de l'image:
        self.dessin.pos = self.pos
 
    def move(self):
        #On recalcule les positions:
        self.y=self.y+self.dy
        #On teste la fin de la chute:
        if self.y<=0-self.size[1]:
            #Repositionnement aleatoire en haut:
            self.y=Window.size[1]
            self.x=random.randint(0,int(Window.size[0]-self.size[0]))
   
    def prise(self):#Changement d'image pour le succes:
        self.dy=0#On stoppe la chute
        #Position au dessus du panier pour stopper la collision:
        self.y=Window.size[1]*0.2  
        self.dessin.source='YES.png'#Nouvelle image
        #On lance le nouveau lapin dans 0.5 seconde:
        Clock.schedule_once(self.prise_fin, 0.5)
   
    def prise_fin(self,dt):#Retour a l'image de lapin et en haut:
        self.y=0-self.size[1]#On le place en dessous pour qu'il remonte
        self.dessin.source='lapin.png'   #On change l'image
        self.dy=-10    #On relance la chute
 
class Panier(Widget):
    def __init__(self,canvas):
        self.canvas=canvas
        #Taille et position:
        self.size=(Window.size[0]*0.1,Window.size[1]*0.1)
        self.pos=(0,Window.size[1]*0.02)
        #Ajout de l'image (add_wiget fonctionne aussi):
        with self.canvas:
            self.dessin = Rectangle(source='panier.png',size=self.size, pos=self.pos)
        #On associe le mouvement du panier et son image:
        self.bind(pos=self.update_canvas)
 
    def update_canvas(self, *args):#Mise a jour des positions de l'image:
        self.dessin.pos = self.pos
 
class Jeu(FloatLayout):
    def debut(self):
        #On recupere la taille de l'ecran:
        self.size=Window.size
        #On charge les sons :
        self.son_yes = SoundLoader.load('YES.ogg')
        #Une image de fond:
        self.add_widget(Image(source='fond1.jpg',allow_stretch=True,keep_ratio=False))
       
        #Un label pour le score:
        self.score=0#Creation de la variable score
        self.label=Label(text='Scrore : '+str(self.score),markup=True)
        #Taille de la police en fonction de l'ecran:
        self.label.font_size=self.size[0]*0.05
        #Le label ne doit pas ecraser tout l'ecran:
        self.label.size_hint=(None,None)
        #Position du label vers le centre de l'ecran:
        self.label.pos=(Window.size[0]*0.47,Window.size[1]*0.45)
        self.label.color=[0,0,0,1]
        #On ajoute le label dans l'ecran du jeu:
        self.add_widget(self.label)
       
        #Creation du panier:
        self.panier=Panier(self.canvas)
        #Creation des lapins:
        self.lapins=[]
        for i in range(0,5):#On ajoute les lapins
            self.lapins.append(Lapin(self.canvas))
       
        #Creation des chiffres pour de depart:
        self.compteur_anim=3
        self.chiffre=Image(source='3.png',allow_stretch=True,keep_ratio=False)
        self.chiffre.size_hint=(0,0)
        self.chiffre.pos=self.center
        self.add_widget(self.chiffre)
        #Lancement de l'animation start:
        self.animation_start()
       
        #Depart de l'horloge du jeu:
        Clock.schedule_interval(self.update_chute, 4.0/100.0)
 
    def update_chute(self,dt):#Chute des lapins et tests de collisions
        for lapin in self.lapins:
            lapin.move()
            if lapin.collide_widget(self.panier):
                    lapin.prise()#Animation de la capture
                    self.score+=1
                    self.label.text='Scrore : '+str(self.score)
                    self.son_yes.play()
 
    def on_touch_move(self,touch):#Deplacement du panier
        if touch.y<self.size[1]/3:
            self.panier.center_x=touch.x
   
    def animation_start(self):#Depart de l'animation start
        #On compose l'animation avec deux anim successives:
        anim = Animation(pos=(0,0),size=self.size,t='in_quad',duration=0.8)
        anim += Animation(pos=self.center,size=(0,0),duration=0.8)
        #On prevoie de lancer le prochain chiffre a la fin de l'animation:
        anim.bind(on_complete=self.animation_next)
        #Depart de l'animation:
        anim.start(self.chiffre)
       
    def animation_next(self,animation,widget):#Animation suivante:
        self.compteur_anim-=1
        if self.compteur_anim==0:
            self.remove_widget(self.chiffre)
        else:
            self.chiffre.source=str(self.compteur_anim)+'.png'
            self.animation_start()  
 
class LapinsApp(App):
    def build(self):
        New_game=Jeu()#Creation du jeu
        New_game.debut()#Initialisation du jeu
        return New_game#Envoie de l'affichage
 
if __name__ == '__main__':
    LapinsApp().run()

Télécharger

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" :

from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button

Télécharger

Ensuite, on créé les deux écrans :

#On declare deux ecrans 'Menu' et 'Game'
class MenuScreen(Screen):
    def build(self):
        self.name='Menu'#On donne un nom a l'ecran

class GameScreen(Screen):
    def build(self):
        self.name='Game'#On donne un nom a l'ecran

Télécharger

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 :

# Creation du screen manager
sm = ScreenManager()

class LapinsApp(App):
    def build(self):
        Menu=MenuScreen()#Creation de l'ecran 'Menu'
        Menu.build()#Construction de l'ecran 'Menu'
        #On ajoute l'ecran dans le screen manager
        sm.add_widget(Menu)
        sm.current='Menu'#On definit 'Menu' comme ecran courant
        return sm #On envoie le screen manager pour affichage

Télécharger

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 :

class MenuScreen(Screen):
    def build(self):
        self.name='Menu'#On donne un nom a l'ecran
        #Une image de fond:
        self.add_widget(Image(source='land.png',allow_stretch=True,keep_ratio=False))
        #On definie un layout pour cet ecran:
        Menu_Layout = BoxLayout(padding=100,spacing=10,orientation='vertical')
        #On cree un bouton pour lancer le jeu:
        self.Bouton_Jeu=Button(text='Jouer!')
        self.Bouton_Jeu.font_size=Window.size[0]*0.05
        self.Bouton_Jeu.background_color=[0,0,0,0.2]
        self.Bouton_Jeu.bind(on_press=self.Vers_Game)
        #On ajoute le bouton dans l'affichage:
        Menu_Layout.add_widget(self.Bouton_Jeu)
        #On cree un bouton qui ne fait rien pour l'instant:
        self.Bouton_Rien=Button(text='Je ne fais rien !')
        self.Bouton_Rien.font_size=Window.size[0]*0.05
        self.Bouton_Rien.background_color=[0,0,0,0.2]
        self.Bouton_Rien.bind(on_press=self.Vers_Rien)
        #On ajoute le bouton dans l'affichage:
        Menu_Layout.add_widget(self.Bouton_Rien)
        #On ajoute ce layout dans l'ecran:
        self.add_widget(Menu_Layout)

    def Vers_Game(self,instance):#Fonction de transition vers 'Game'
        pass
   
    def Vers_Rien(self, instance):
        pass

Télécharger

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 :
def Vers_Game(self,instance):#Fonction de transition vers 'Game'
    Game=GameScreen()
    Game.build()#On construit l'ecran 'Game'
    sm.add_widget(Game)#On ajoute l'ecran dans le screen manager
    sm.current='Game'#On definit 'Menu' comme ecran courant

Télécharger

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 :

class GameScreen(Screen):
    def build(self):
        self.name='Game'#On donne un nom a l'ecran
        Game_Layout=Jeu()#Creation du jeu
        Game_Layout.debut()#Initialisation du jeu
        self.add_widget(Game_Layout)#On l'ajoute dans l'ecran

Télécharger

Voici au final votre code :

from kivy.app import App
from kivy.core.window import Window
from kivy.core.window import WindowBase
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import Image
from kivy.graphics import Rectangle
from kivy.uix.widget import Widget
from kivy.clock import Clock
from kivy.uix.label import Label
from kivy.animation import Animation
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.core.audio import SoundLoader
#Nombres aleatoires:
import random

#On declare deux ecrans 'Menu' et 'Game'
class MenuScreen(Screen):
    def build(self):
        self.name='Menu'#On donne un nom a l'ecran
        #Une image de fond:
        self.add_widget(Image(source='land.png',allow_stretch=True,keep_ratio=False))
        #On definie un layout pour cet ecran:
        Menu_Layout = BoxLayout(padding=100,spacing=10,orientation='vertical')
        #On cree un bouton pour lancer le jeu:
        self.Bouton_Jeu=Button(text='Jouer!')
        self.Bouton_Jeu.font_size=Window.size[0]*0.05
        self.Bouton_Jeu.background_color=[0,0,0,0.2]
        self.Bouton_Jeu.bind(on_press=self.Vers_Game)
        #On ajoute le bouton dans l'affichage:
        Menu_Layout.add_widget(self.Bouton_Jeu)
        #On cree un bouton qui ne fait rien pour l'instant:
        self.Bouton_Rien=Button(text='Je ne fais rien !')
        self.Bouton_Rien.font_size=Window.size[0]*0.05
        self.Bouton_Rien.background_color=[0,0,0,0.2]
        self.Bouton_Rien.bind(on_press=self.Vers_Rien)
        #On ajoute le bouton dans l'affichage:
        Menu_Layout.add_widget(self.Bouton_Rien)
        #On ajoute ce layout dans l'ecran:
        self.add_widget(Menu_Layout)

    def Vers_Game(self,instance):#Fonction de transition vers 'Game'
        Game=GameScreen()
        Game.build()#On construit l'ecran 'Game'
        sm.add_widget(Game)#On ajoute l'ecran dans le screen manager
        sm.current='Game'#On definit 'Menu' comme ecran courant
   
    def Vers_Rien(self, instance):
        pass

class GameScreen(Screen):
    def build(self):
        self.name='Game'#On donne un nom a l'ecran
        Game_Layout=Jeu()#Creation du jeu
        Game_Layout.debut()#Initialisation du jeu
        self.add_widget(Game_Layout)#On l'ajoute dans l'ecran

class Lapin(Widget):
    def __init__(self,canvas):
        self.dy=-10
        self.canvas=canvas
        #Taille et position aleatoire:
        self.size=(Window.size[0]*0.05,Window.size[1]*0.1)
        self.x = random.randint(0,int(Window.size[0]-self.size[0]))
        self.y=Window.size[1]-100
        #Ajout de l'image du lapin:
        with self.canvas:
            self.dessin = Rectangle(source='lapin.png',size=self.size, pos=self.pos)
        #Detection des mouvements:
        self.bind(pos=self.update_canvas)
 
    def update_canvas(self, *args):#Mise a jour des positions de l'image:
        self.dessin.pos = self.pos
 
    def move(self):
        #On recalcule les positions:
        self.y=self.y+self.dy
        #On teste la fin de la chute:
        if self.y<=0-self.size[1]:
            #Repositionnement aleatoire en haut:
            self.y=Window.size[1]
            self.x=random.randint(0,int(Window.size[0]-self.size[0]))
   
    def prise(self):#Changement d'image pour le succes:
        self.dy=0#On stoppe la chute
        #Position au dessus du panier pour stopper la collision:
        self.y=Window.size[1]*0.2  
        self.dessin.source='YES.png'#Nouvelle image
        #On lance le nouveau lapin dans 0.5 seconde:
        Clock.schedule_once(self.prise_fin, 0.5)
   
    def prise_fin(self,dt):#Retour a l'image de lapin et en haut:
        self.y=0-self.size[1]#On le place en dessous pour qu'il remonte
        self.dessin.source='lapin.png'   #On change l'image
        self.dy=-10    #On relance la chute
 
class Panier(Widget):
    def __init__(self,canvas):
        self.canvas=canvas
        #Taille et position:
        self.size=(Window.size[0]*0.1,Window.size[1]*0.1)
        self.pos=(0,Window.size[1]*0.02)
        #Ajout de l'image (add_wiget fonctionne aussi):
        with self.canvas:
            self.dessin = Rectangle(source='panier.png',size=self.size, pos=self.pos)
        #On associe le mouvement du panier et son image:
        self.bind(pos=self.update_canvas)
 
    def update_canvas(self, *args):#Mise a jour des positions de l'image:
        self.dessin.pos = self.pos
 
class Jeu(FloatLayout):
    def debut(self):
        #On recupere la taille de l'ecran:
        self.size=Window.size
        #On charge les sons :
        self.son_yes = SoundLoader.load('YES.ogg')
        #Une image de fond:
        self.add_widget(Image(source='fond1.jpg',allow_stretch=True,keep_ratio=False))
       
        #Un label pour le score:
        self.score=0#Creation de la variable score
        self.label=Label(text='Scrore : '+str(self.score),markup=True)
        #Taille de la police en fonction de l'ecran:
        self.label.font_size=self.size[0]*0.05
        #Le label ne doit pas ecraser tout l'ecran:
        self.label.size_hint=(None,None)
        #Position du label vers le centre de l'ecran:
        self.label.pos=(Window.size[0]*0.47,Window.size[1]*0.45)
        self.label.color=[0,0,0,1]
        #On ajoute le label dans l'ecran du jeu:
        self.add_widget(self.label)
       
        #Creation du panier:
        self.panier=Panier(self.canvas)
        #Creation des lapins:
        self.lapins=[]
        for i in range(0,5):#On ajoute les lapins
            self.lapins.append(Lapin(self.canvas))
       
        #Creation des chiffres pour de depart:
        self.compteur_anim=3
        self.chiffre=Image(source='3.png',allow_stretch=True,keep_ratio=False)
        self.chiffre.size_hint=(0,0)
        self.chiffre.pos=self.center
        self.add_widget(self.chiffre)
        #Lancement de l'animation start:
        self.animation_start()
       
        #Depart de l'horloge du jeu:
        Clock.schedule_interval(self.update_chute, 4.0/100.0)
 
    def update_chute(self,dt):#Chute des lapins et tests de collisions
        for lapin in self.lapins:
            lapin.move()
            if lapin.collide_widget(self.panier):
                    lapin.prise()#Animation de la capture
                    self.score+=1
                    self.label.text='Scrore : '+str(self.score)
                    self.son_yes.play()
 
    def on_touch_move(self,touch):#Deplacement du panier
        if touch.y<self.size[1]/3:
            self.panier.center_x=touch.x
   
    def animation_start(self):#Depart de l'animation start
        #On compose l'animation avec deux anim successives:
        anim = Animation(pos=(0,0),size=self.size,t='in_quad',duration=0.8)
        anim += Animation(pos=self.center,size=(0,0),duration=0.8)
        #On prevoie de lancer le prochain chiffre a la fin de l'animation:
        anim.bind(on_complete=self.animation_next)
        #Depart de l'animation:
        anim.start(self.chiffre)
       
    def animation_next(self,animation,widget):#Animation suivante:
        self.compteur_anim-=1
        if self.compteur_anim==0:
            self.remove_widget(self.chiffre)
        else:
            self.chiffre.source=str(self.compteur_anim)+'.png'
            self.animation_start()  
   
# Creation du screen manager
sm = ScreenManager()

class LapinsApp(App):
    def build(self):
        Menu=MenuScreen()#Creation de l'ecran 'Menu'
        Menu.build()#Construction de l'ecran 'Menu'
        #On ajoute l'ecran dans le screen manager
        sm.add_widget(Menu)
        sm.current='Menu'#On definit 'Menu' comme ecran courant
        return sm #On envoie le screen manager pour affichage
 
if __name__ == '__main__':
    LapinsApp().run()

Télécharger

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...)