Formation I.S.N.

Animations automatiques

Animer un objet graphique revient faire du dessin animé : on redéfinit sa position (donc on redéfinit les paramètres de l'objet) après une action de l'utilisateur (un événement déclenché par l'utilisateur) ou après un laps de temps prédéfini (on parle dans ce cas d'animation automatique).

Une animation deviendra automatique lorsque la fonction qui «pilote» la redéfinition des objets graphiques fait appel à elle-même après un cours laps de temps. La méthode .after() permet ce type d'appel récursif.

L'instruction :


fenetre.after(temps, fonction)
					

relance l'action de fonction dans la fenetre toutes les temps millisecondes.

La balle qui rebondit...

Un cercle (la balle) se déplace automatiquement dans une direction à l'intérieur d'un Canvas. Lorsque le cercle arrive au bord du Canvas, il « rebondit » et repart dans la direction opposée, etc...

L'interface graphique définie par le programme ci-dessous est constituée d'un Canvas de dimensions 242*242 et de trois Button permettant de [Démarrer] l'animation, [Arrêter] l'animation et [Quitter]. Le cercle rouge, de rayon 10 pixels, a pour centre le point de coordonnées initiales x0 = 122 et y0 = 122.

Le « pas » du déplacement est désigné par dx (horizontal) et dy (vertical).

Ce programme incomplet doit faire appel à trois fonctions :

  • start() qui lance (autorise) l'animation après clic sur [Démarrer].
  • stop() qui stoppe l'animation après un clic sur [Arrêter].
  • move() qui, au bout de 30 millisecondes (par exemple), détermine les nouvelles coordonnées du centre du cercle puis « retrace » ce cercle.

##----- Importation des Modules -----##
from tkinter import *


##----- Variables globales et conditions initiales -----##
l, h = 242, 242												# Dimensions du canevas
dx, dy = 0, 0                                               # "pas" initial du déplacement
x0, y0 = l//2, h//2                                         # Coordonnées initiales du cercle
r = 10                                                      # Rayon du cercle


##----- Fonction générale pilotant les déplacements -----##
def move():
    """ Entrées : Fonction déclenchée par le bouton [Démarrer]- pas d'entrée
        Sorties : Fonction récursive qui redéfinit les coordonnées du centre
            du cercle toutes les 30 ms, à condition que le drapeau soit levé"""
	

##----- Fonctions réceptionnant les événements -----##
def stop():
    """Cette fonction baisse le drapeau et arrête l'animation."""


def start():
    """Cette fonction lève le drapeau et lance l'animation."""



##----- Création de la fenêtre -----##
fen = Tk()
fen.title('Balle qui rebondit')


##----- Création des boutons -----##
bouton_quitter = Button(fen, text="Quitter", width=9, command=fen.destroy)
bouton_quitter.grid(row = 1, column = 2, padx=3, pady=3)

bouton_stop = Button(fen, text="Arrêter", width=9)
bouton_stop.grid(row = 1, column = 1, padx=3, pady=3)

bouton_start = Button(fen, text="Démarrer", width=9)
bouton_start.grid(row = 1, column = 0, padx=3, pady=3)


##----- Création du canevas -----##
dessin = Canvas(fen, bg="white", width=l, height=h)
dessin.grid(row = 0, column = 0, columnspan = 3, padx=3, pady=3)


##----- Objets graphiques -----##
cercle = dessin.create_oval(x0-r, y0-r, x0+r, y0+r, width=2, outline="red")


##----- Programme principal -----##
move()

fen.mainloop()                    # Boucle d'attente des événements
				

Le cercle se déplace horizontalement de gauche à droite

  1. Que peut-on affirmer sur dx et dy ?
  2. Quelle est l'abscisse « limite » du centre du cercle avant que la balle ne rebondisse vers la gauche ?
  3. Comment obliger le cercle à repartir « dans l'autre sens » ?
  4. Quelle est l'abscisse « limite » à gauche du Canvas ?
  5. On pose dx = 5 et dy = 0. Complétez la définition de la fonction move() pour que le cercle se déplace automatiquement vers la droite.
  6. Améliorez cette définition pour que le cercle rebondisse alternativement de droite à gauche puis de gauche à droite.
  • Question 1°/
  • Question 2°/
  • Question 3°/
  • Question 4°/
  • La fonction move()
  • Une solution
dx et dy sont des constantes globales entières.

On désigne par :

  • l la largeur du canevas ;
  • r le rayon du cercle ;
  • dx le pas horizontal du déplacement.

Alors l'abscisse limite du centre du cercle avant que celui-ci ne reparte vers la gauche est idéalement obtenue par la comparaison avec l-r (sans tenir compte de l'épaisseur du cercle).

En posant dx = -dx, le déplacement « vers la droite » devient déplacement « vers la gauche ».

On désigne par :

  • l la largeur du canevas ;
  • r le rayon du cercle ;
  • dx le pas horizontal du déplacement.

Alors l'abscisse limite du centre du cercle avant que celui-ci ne reparte vers la droite est idéalement obtenue par la comparaison avec r (sans tenir compte de l'épaisseur du cercle).


##----- Fonction générale pilotant les déplacements -----##
def move():
    """ Entrées : Fonction déclenchée par le bouton [Démarrer]- pas d'entrée
        Sorties : Fonction récursive qui redéfinit les coordonnées du centre
            du cercle toutes les 30 ms, à condition que le drapeau soit levé"""
    global x0, y0, dx, dy, l, r
    x0 = x0+dx                                          # Nouvelle abscisse du centre du cercle
    dessin.coords(cercle, x0-r, y0-r, x0+r, y0+r)
    if x0 >= l-r or x0 <= r:                            # Bord droit ou bord gauche atteint,
        dx = -dx                                        # le déplacement s'effectue dans l'autre sens
    fen.after(30, move)
						

##----- Importation des Modules -----##
from tkinter import *


##----- Variables globales et conditions initiales -----##
l, h = 242, 242												# Dimensions du canevas
dx, dy = 5, 0                                               # "pas" initial du déplacement
x0, y0 = l//2, h//2                                         # Coordonnées initiales du cercle
r = 10                                                      # Rayon du cercle


##----- Fonction générale pilotant les déplacements -----##
def move():
    """ Entrées : Fonction déclenchée par le bouton [Démarrer]- pas d'entrée
        Sorties : Fonction récursive qui redéfinit les coordonnées du centre
            du cercle toutes les 30 ms, à condition que le drapeau soit levé"""
    global x0, y0, dx, dy, l, r
    x0 = x0+dx                                          # Nouvelle abscisse du centre du cercle
    dessin.coords(cercle, x0-r, y0-r, x0+r, y0+r)
    if x0 >= l-r or x0 <= r:                            # Bord droit ou bord gauche atteint,
        dx = -dx                                        # le déplacement s'effectue dans l'autre sens
    fen.after(30, move)


##----- Fonctions réceptionnant les événements -----##
def stop():
    """Cette fonction baisse le drapeau et arrête l'animation."""


def start():
    """Cette fonction lève le drapeau et lance l'animation."""



##----- Création de la fenêtre -----##
fen = Tk()
fen.title('Balle qui rebondit')


##----- Création des boutons -----##
bouton_quitter = Button(fen, text="Quitter", width=9, command=fen.destroy)
bouton_quitter.grid(row = 1, column = 2, padx=3, pady=3)

bouton_stop = Button(fen, text="Arrêter", width=9)
bouton_stop.grid(row = 1, column = 1, padx=3, pady=3)

bouton_start = Button(fen, text="Démarrer", width=9)
bouton_start.grid(row = 1, column = 0, padx=3, pady=3)


##----- Création du canevas -----##
dessin = Canvas(fen, bg="white", width=l, height=h)
dessin.grid(row = 0, column = 0, columnspan = 3, padx=3, pady=3)


##----- Objets graphiques -----##
cercle = dessin.create_oval(x0-r, y0-r, x0+r, y0+r, width=2, outline="red")


##----- Programme principal -----##
move()

fen.mainloop()                    # Boucle d'attente des événements
						

On lance et on stoppe l'animation

Pour autoriser ou interdire une animation, on utilise généralement un drapeau, c'est-à-dire une variable globale booléenne.

  1. Quelle doit être la valeur initiale de la variable drapeau ?
  2. Définir cette variable puis complétez les définitions des fonctions start() et stop() qui doivent modifier la valeur du drapeau.
  3. Améliorez la définition de la fonction move() pour vérifier qu'il est possible de stopper puis redémarrer plusieurs fois l'animation.
  4. Au fait, en cliquant plusieurs fois de suite sur le bouton [Démarrer], le cercle accélère-t-il ? Si oui, comment y remédier ?
  • Question 1°/
  • Fonctions start() et stop()
  • Fonction move() améliorée
  • Une solution
La variable booléenne drapeau doit avoir pour valeur initiale >False afin d'interdire l'animation au démarrage de l'application.

Il ne faut pas oublier d'associer les fonctions start() et stop() aux boutons correspondants. Si votre fonction start est différente de celle ci-dessous, elle n'est peut-être pas fausse. Jetez un coup d'oeil à la solution.


##----- Fonctions réceptionnant les événements -----##
def stop():
    """Cette fonction baisse le drapeau et arrête l'animation."""
    global drapeau
    drapeau = False


def start():
    """Cette fonction lève le drapeau et lance l'animation."""
    global drapeau
    drapeau = True
    move()
						

##----- Fonction générale pilotant les déplacements -----##
def move():
    """ Entrées : Fonction déclenchée par le bouton [Démarrer]- pas d'entrée
        Sorties : Fonction récursive qui redéfinit les coordonnées du centre
            du cercle toutes les 30 ms, à condition que le drapeau soit levé"""
    global x0, y0, dx, dy, l, r
    x0 = x0+dx                                          # Nouvelle abscisse du centre du cercle
    dessin.coords(cercle, x0-r, y0-r, x0+r, y0+r)
    if x0 >= l-r or x0 <= r:                            # Bord droit ou bord gauche atteint,
        dx = -dx                                        # le déplacement s'effectue dans l'autre sens
    if drapeau:
        fen.after(30, move)
						

Si le cercle accélère, c'est la fonction start qui est en cause. En effet, lorsqu'on clique sur le bouton [Démarrer], on appelle à nouveau une instance de la fonction move() qui est récursive. Il y a donc empilement des appels donc déplacement plus rapide du cercle à l'écran : ses coordonnées sont recalculées plus souvent.


##----- Importation des Modules -----##
from tkinter import *


##----- Variables globales et conditions initiales -----##
l, h = 242, 242												# Dimensions du canevas
dx, dy = 5, 0                                               # "pas" initial du déplacement
x0, y0 = l//2, h//2                                         # Coordonnées initiales du cercle
r = 10                                                      # Rayon du cercle
drapeau = False


##----- Fonction générale pilotant les déplacements -----##
def move():
    """ Entrées : Fonction déclenchée par le bouton [Démarrer]- pas d'entrée
        Sorties : Fonction récursive qui redéfinit les coordonnées du centre
            du cercle toutes les 30 ms, à condition que le drapeau soit levé"""
    global x0, y0, dx, dy, l, r
    x0 = x0+dx                                          # Nouvelle abscisse du centre du cercle
    dessin.coords(cercle, x0-r, y0-r, x0+r, y0+r)
    if x0 >= l-r or x0 <= r:                            # Bord droit ou bord gauche atteint,
        dx = -dx                                        # le déplacement s'effectue dans l'autre sens
    if drapeau:
        fen.after(30, move)


##----- Fonctions réceptionnant les événements -----##
def stop():
    """Cette fonction baisse le drapeau et arrête l'animation."""
    global drapeau
    drapeau = False


def start():
    """Cette fonction lève le drapeau et lance l'animation."""
    global drapeau
    if drapeau == False:                                 # Nécessaire pour ne pas lancer plusieurs fois l'animation
        drapeau = True
        move()


##----- Création de la fenêtre -----##
fen = Tk()
fen.title('Balle qui rebondit')


##----- Création des boutons -----##
bouton_quitter = Button(fen, text="Quitter", width=9, command=fen.destroy)
bouton_quitter.grid(row = 1, column = 2, padx=3, pady=3)

bouton_stop = Button(fen, text="Arrêter", width=9, command=stop)
bouton_stop.grid(row = 1, column = 1, padx=3, pady=3)

bouton_start = Button(fen, text="Démarrer", width=9, command=start)
bouton_start.grid(row = 1, column = 0, padx=3, pady=3)


##----- Création du canevas -----##
dessin = Canvas(fen, bg="white", width=l, height=h)
dessin.grid(row = 0, column = 0, columnspan = 3, padx=3, pady=3)


##----- Objets graphiques -----##
cercle = dessin.create_oval(x0-r, y0-r, x0+r, y0+r, width=2, outline="red")


##----- Programme principal -----##
move()

fen.mainloop()                    # Boucle d'attente des événements
						

Des mouvements dans tous les sens

  1. Quelles sont les ordonnées « limite » en haut et en bas du Canvas ?
  2. Modifiez la définition de la fonction move(), ainsi que la valeur initiale de dy afin que la balle puisse effectuer des mouvements verticaux (voire en « diagonale »).
  3. Enfin, améliorez le programme pour que les valeurs initiales de dx et dy soient des entiers aléatoires entre 1 et 10.
    Vérifiez que la balle effectue ainsi des mouvements obliques et qu'elle rebondit comme une boule de billard dans le Canvas.
  • Question 1°/
  • Fonction move() finale
  • Le programme complet

On désigne par :

  • h la hauteur du canevas ;
  • r le rayon du cercle ;
  • dy le pas vertical du déplacement.

Alors l'ordonnée limite du centre du cercle avant que celui-ci ne reparte vers le bas est idéalement obtenue par la comparaison avec r (sans tenir compte de l'épaisseur du cercle).

Et l'ordonnée limite du centre du cercle avant que celui-ci ne reparte vers le haut est idéalement obtenue par la comparaison avec h-r (sans tenir compte de l'épaisseur du cercle).


##----- Fonction générale pilotant les déplacements -----##
def move():
    """ Entrées : Fonction déclenchée par le bouton [Démarrer]- pas d'entrée
        Sorties : Fonction récursive qui redéfinit les coordonnées du centre
            du cercle toutes les 30 ms, à condition que le drapeau soit levé"""
    global x0, y0, dx, dy, l, h, r
    x0 = x0+dx                                          # Nouvelle abscisse du centre du cercle
    y0 = y0+dy                                          # Nouvelle ordonnée du centre du cercle
    dessin.coords(cercle, x0-r, y0-r, x0+r, y0+r)
    if x0 >= l-r or x0 <= r:                            # Bord droit ou bord gauche atteint,
        dx = -dx                                        # le déplacement s'effectue dans l'autre sens
    if y0 >= h-r or y0 <= r:                            # Bord bas ou bord haut atteint,
        dy = -dy
    if drapeau:
        fen.after(30, move)
						

##----- Importation des Modules -----##
from tkinter import *
from random import *


##----- Variables globales et conditions initiales -----##
l, h = 242, 242												# Dimensions du canevas
dx, dy = randint(1, 10), randint(1, 10)                     # "pas" initial du déplacement
x0, y0 = l//2, h//2                                         # Coordonnées initiales du cercle
r = 10                                                      # Rayon du cercle
drapeau = False


##----- Fonction générale pilotant les déplacements -----##
def move():
    """ Entrées : Fonction déclenchée par le bouton [Démarrer]- pas d'entrée
        Sorties : Fonction récursive qui redéfinit les coordonnées du centre
            du cercle toutes les 30 ms, à condition que le drapeau soit levé"""
    global x0, y0, dx, dy, l, h, r
    x0 = x0+dx                                          # Nouvelle abscisse du centre du cercle
    y0 = y0+dy                                          # Nouvelle ordonnée du centre du cercle
    dessin.coords(cercle, x0-r, y0-r, x0+r, y0+r)
    if x0 >= l-r or x0 <= r:                            # Bord droit ou bord gauche atteint,
        dx = -dx                                        # le déplacement s'effectue dans l'autre sens
    if y0 >= h-r or y0 <= r:                            # Bord bas ou bord haut atteint,
        dy = -dy
    if drapeau:
        fen.after(30, move)


##----- Fonctions réceptionnant les événements -----##
def stop():
    """Cette fonction baisse le drapeau et arrête l'animation."""
    global drapeau
    drapeau = False


def start():
    """Cette fonction lève le drapeau et lance l'animation."""
    global drapeau
    if drapeau == False:                                 # Nécessaire pour ne pas lancer plusieurs fois l'animation
        drapeau = True
        move()


##----- Création de la fenêtre -----##
fen = Tk()
fen.title('Balle qui rebondit')


##----- Création des boutons -----##
bouton_quitter = Button(fen, text="Quitter", width=9, command=fen.destroy)
bouton_quitter.grid(row = 1, column = 2, padx=3, pady=3)

bouton_stop = Button(fen, text="Arrêter", width=9, command=stop)
bouton_stop.grid(row = 1, column = 1, padx=3, pady=3)

bouton_start = Button(fen, text="Démarrer", width=9, command=start)
bouton_start.grid(row = 1, column = 0, padx=3, pady=3)


##----- Création du canevas -----##
dessin = Canvas(fen, bg="white", width=l, height=h)
dessin.grid(row = 0, column = 0, columnspan = 3, padx=3, pady=3)


##----- Objets graphiques -----##
cercle = dessin.create_oval(x0-r, y0-r, x0+r, y0+r, width=2, outline="red")


##----- Programme principal -----##
move()

fen.mainloop()                    # Boucle d'attente des événements