deutsch     english    français     Imprimer

 

7.1 DES OBJETS, ENCORE DES OBJETS

 

 

INTRODUCTION

 

Dans notre quotidien, nous sommes entourés d’une multitude d’objets. Du fait que les logiciels sont souvent conçus à l’image de la réalité, il est très naturel d’introduire la notion d’objets en informatique. C’est ce qu’on appelle la programmation orientée objets (POO). Depuis plusieurs décennies, la notion de POO s’est révélée être une révolution dans le génie logiciel à tel point que pratiquement n’importe quel logiciel significatif est actuellement développé en utilisant cette technique [plue...On distingue les langages purement orientés objets tels que Java et les langages hybrides (multi-paradigmes) tels que C++ ou Python permettant de mélanger la programmation orientée objets avec de la programmation impérative]. In the following chapter you will learn the main concepts of OOP so that you can participate in the hype.

Nous avons déjà vu que les tortues sont représentées par des objets. Une tortue possède certaines propriétés comme sa couleur, sa position et son angle de visée ainsi que certains comportements, comme la capacité d’avancer, de tourner, etc. En POO, des objets possédant des propriétés et des comportements communs sont regroupés par classes. Les objets tortues appartiennent tous à la classe Turtle : on dit en termes techniques qu’ils sont des instances de la classe Turtle. Afin de créer un objet, il faut utiliser une classe prédéfinie ou définir une nouvelle classe.

En termes techniques, les propriétés des objets sont également appelées attributs ou variables d’instance et les capacité sont souvent appelés méthodes ou opérations. Ces attributs et méthodes sont en fait des variables et des fonctions à l’exception qu’elles sont encapsulées dans la classe. Pour s’y référer de l’extérieur de la classe, il suffit de préfixer leur nom par celui d’une instance de classe et de l’opérateur point.

CONCEPTS DE PROGRAMMATION:
Classe, objet (instance), propriété, capacité, attribut / variable d’instance, méthode, héritage, classe de base (parente), constructeur

 

 

UNE FENÊTRE DE JEU ADAPTIVE

 

Développer un jeu vidéo sans recourir à la programmation orientée objets est un véritable supplice de Sisyphe du fait que les acteurs du jeu et tous les objets qui interviennent dans leur environnement sont justement des objets en interaction. Dans un jeu en 2D, le plateau de jeu est une fenêtre rectangulaire représentée par la classe GameGrid de la bibliothèque JGameGrid. TigerJython fabrique une instance de cette classe GameGrid lors de l’appel de la fonction makeGameGrid() et affiche la fenêtre lors d’un appel à la fonction show(). Il est possible de personnaliser l’apparence de la fenêtre de jeu avec des paramètres appropriés.

L’appel makeGameGrid(10, 10, 60, Color.red, "sprites/town.jpg", False) aura par exemple pour effet d’afficher une fenêtre de jeu de taille 10x10 cellules carrées ayant chacune 60 pixels de côté. Le paramètre Color.red indique la couleur du quadrillage et le cinquième paramètre indique qu’il faut utiliser town.jpg en guise d’image de fond. Le dernier paramètre booléen désactive la barre de navigation, ce qui n’est pas nécessaire dans notre cas.

 

from gamegrid import *

makeGameGrid(10, 10, 60, Color.red, "sprites/town.jpg", False)
show()
Sélectionner le code (Ctrl+C pour copier, Ctrl+V pour coller)

 

 

MEMENTO

 

Les méthodes de la classe GameGrid sont disponibles en tant que fonctions lorsque l’on crée la fenêtre de jeu avec makeGameGrid(). On peut cependant également créer soi-même une instance manuellement et appeler les méthodes à l’aide de l’opérateur point.

from gamegrid import *

gg = GameGrid(10, 10, 60, Color.red, "sprites/town.jpg", False)
gg.show()

La fenêtre de jeu est constituée de 10x10 cellules carrées dont la taille est de 60 pixels. Puisque les lignes de la grille sont également affichées tout en bas et tout à droite, la fenêtre a en fait une taille de 601 x 601 pixels. Cela correspondant à la taille minimale de l’image d’arrière-plan.

Le dernier paramètre booléen détermine s’il faut afficher une barre de navigation.

 

 

DÉFINIR UNE CLASSE PAR DÉRIVATION (HÉRITAGE)

 

Lorsque l’on définit une classe, on peut choisir si celle-ci est indépendante des autres ou si, au contraire, il s’agit d’une classe dérivée d’une classe déjà existante. Toutes les propriétés et méthodes de la classe parente aussi appelée classe de base ou superclasse sont disponibles dans la classe fille (dérivée). Autrement dit, la classe dérivée (ou sous-classe) hérite des propriétés et des méthodes de ses classes parentes.

Dans la classe JGameGrid, les personnages de jeu sont appelées acteurs et sont des instances de la classe prédéfinie Actor. Pour définir son propre personnage, si suffit de définir une classe dérivée de la classe Actor.

La définition d’une classe débute pas l’usage du mot-clé class suivi du nom attribué à cette nouvelle classe ainsi qu’une paire de parenthèses. Entre parenthèses, on note le nom d’une ou plusieurs classes qui feront office de classes parentes. Comme on veut dériver notre personnage de la classe Actor, c’est ce que l’on indiquera entre parenthèses suivants le nom de la classe.

La définition de la classe contient la définition de ses méthodes qui ne sont rien d’autre que des fonctions dont la seule particularité est de prendre self comme premier paramètre. Ce paramètre self permet d’accéder, depuis le code encapsulé dans la classe, aux autres méthodes et variables d’instance présentes au sein de l’objet.

On débute généralement la définition d’une classe par une méthode spéciale __init__(self, …) reconnaissable aux doubles caractères de soulignement qui marquent le début et la fin du init. Cette méthode spéciale, nommée constructeur, est invoquée automatiquement lors de la création d’un objet de la classe concernée. Dans le programme ci-dessous, on appelle le constructeur de la classe de base Actor depuis le constructeur de la classe Alien à laquelle il faut spécifier le chemin d’accès au sprite choisi.

On définit ensuite la méthode act() qui joue une rôle central dans l’animation du jeu puisqu’elle est invoquée par le gestionnaire du jeu lors de chaque cycle de simulation (déplacements). Il s’agit là d’une astuce particulièrement intelligente puisqu’elle permet de ne plus se soucier des animations au sein d’une structure de répétition comme une boucle while.

La méthode act() permet de programmer le comportement d’un acteur à chaque cycle du jeu. Dans notre cas, on ne fait que déplacer l’acteur sur une autre case de la grille avec la fonction move(). Du fait que move() est une méthode héritée de la classe de base Actor, elle fait partie intégrante des instances de la classe Alien, ce qui nous amène à l’invoquer en la préfixant de self.

Une fois la classe Alien définie, on crée un objet de la classe Alien avec
>>> alien_object = Alien()

L’avantage de la POO est qu’il est possible de créer ainsi autant d’aliens (instances de la classe Alien) que nécessaires sans que les données de ceux ne viennent à entrer en conflit dans le programme. Chaque objet a en effet sa propre individualité, comme dans la réalité, de sorte que chaque alien sait exactement comme se déplacer lors de chaque pas de simulation grâce à sa méthode move().

 

On utilise la fonction addActor() pour ajouter au plateau de jeu chacun des aliens générés en spécifiant les coordonnées de sa position de départ dans la grille. La cellule de coordonnées (0,0) est située en haut à gauche de la grille de jeu et l’axe Oy est orienté vers le bas. Pour lancer le cycle de simulation, il faut appeler la fonction doRun().

from gamegrid import *

# ---------------- class Alien ----------------
class Alien(Actor):
    def __init__(self):
        Actor.__init__(self, "sprites/alien.png")
    
    def act(self):
        self.move()

makeGameGrid(10, 10, 60, Color.red, "sprites/town.jpg", False)
spin = Alien() # object creation, many instances can be created
urix = Alien()
addActor(spin, Location(2, 0), 90)
addActor(urix, Location(5, 0), 90)
show()
doRun()
Sélectionner le code (Ctrl+C pour copier, Ctrl+V pour coller)

 

 

MEMENTO

 

La définition d’une classe débute par le mot-clé class et encapsule les méthodes ainsi que les variables d’instance de la classe. Le constructeur de la classe, nommé __init__ est appelé automatiquement par Python lors de la création des objets (instanciations de la classe). Pour créer un objet (ou instance), il faut écrire le nom de la classe et, entre parenthèses, spécifier les arguments demandés par le constructeur.

Tous les personnages de jeu sont dérivés de la classe Actor et leur comportement lors de chaque cycle de simulation est personnalisé dans la méthode dans la méthode act().

La fonction addActor() permet d’ajouter un personnage au plateau de jeu à la position et avec l’angle de départ indiqués en paramètres. L’angle 0 indique une orientation à l’Est (vers la droite) et le sens positif correspond au sens des aiguilles de la montre.

 

 

UNE ATTAQUE D’ALIENS

 

Vous avez pu observer les avantages considérables du paradigme orienté objets en voyant avec quelle facilité on peut peupler le plateau de jeu d’une myriade d’aliens tombant du ciel et cela en très peu de lignes de code.

Dans le programme ci-dessous, on fait en sorte que toutes les 0.2 secondes, un nouvel alien soit créé aléatoirement dans une des cellules de la première ligne du haut.
 
from gamegrid import *
from random import randint

# ---------------- class Alien ----------------
class Alien(Actor):
    def __init__(self):
        Actor.__init__(self, "sprites/alien.png")
    
    def act(self):
        self.move()

makeGameGrid(10, 10, 60, Color.red, "sprites/town.jpg", False)
show()
doRun()

while not isDisposed():
    alien = Alien()
    addActor(alien, Location(randint(0, 9), 0), 90)
    delay(200)
Sélectionner le code (Ctrl+C pour copier, Ctrl+V pour coller)

 

 

MEMENTO

 

Dans le programme principal, afin de garantir une fermeture propre, il faut une boucle qui teste à chaque itération la valeur booléenne retournée par isDisposed() pour savoir si la fenêtre de jeu a été fermée.

Note: Il est parfois nécessaire de fermer TigerJython et de le rouvrir afin de permettre aux sprites et aux images d’arrière-fond de se recharger correctement en cas de modification des fichiers images impliqués.

 

 

SPACE INVADERS LIGHT

 

Dans le premier jeu vidéo que vous allez développer, le joueur doit tenter de repousser une invasion d’aliens en les éliminant d’un clic de souris. Le joueur perd un point par alien qui atterrit avec succès dans la ville.

Pour intégrer le support de la souris dans le programme, il faut ajouter une fonction de rappel pressCallback et l’enregistrer avec le paramètre nommé mousePressed. Cette fonction de rappel commence par obtenir les coordonnées grille du clic en examinant l’objet e reçu en paramètre pour y trouver dans quelle cellule le clic a été effectué. Si cette cellule est occupée par un alien, celui-ci sera retourné par getOneActorAt() tandis que si elle est vide, la valeur None sera retournée. La fonction. removeActor() supprime l’acteur du plateau de jeu.
from gamegrid import *
from random import randint

# ---------------- class Alien ----------------
class Alien(Actor):
    def __init__(self):
        Actor.__init__(self, "sprites/alien.png")
    
    def act(self):
        self.move()

def pressCallback(e):
    location = toLocationInGrid(e.getX(), e.getY())
    actor = getOneActorAt(location)
    if actor != None:
        removeActor(actor)
    refresh()

makeGameGrid(10, 10, 60, Color.red, "sprites/town.jpg", False, 
             mousePressed = pressCallback)
setSimulationPeriod(800)
show()
doRun()

while not isDisposed():
    alien = Alien()
    addActor(alien, Location(randint(0, 9), 0), 90)
    delay(1000)
Sélectionner le code (Ctrl+C pour copier, Ctrl+V pour coller)

 

 

MEMENTO

 

Du fait que act() est appelée une fois par cycle de simulation, la vitesse d’exécution du jeu sera très influencée par ce petit paramètre dont la valeur par défaut est 200 ms. On peut néanmoins changer cette valeur à l’aide de la fonction setSimulationPeriod().

Le rendu du plateau de jeu est reconstruit entièrement à chaque pas de simulation, ce qui implique un court laps de temps de latence entre le moment ou l’état du jeu subit une modification et le moment où le rendu est effectué à l’écran. Si l’on souhaite effectuer le rendu immédiatement lors d’un clic de souris, on peut le lancer manuellement à l’aide de la fonction refresh().

 

 

EXERCICES

 

1.


Créer sa propre image de fond à l’aide d’un éditeur d’images puis l’ajouter au dossier sprites, lui-même présent soit dans le même dossier que le script Python, soit dans le dossier <userhome>/gamegrid/sprites). Il est également possible de spécifier un chemin d’accès absolu.

2.

Ajouter une barre de statut de 30 pixels de haut avec l’appel addStatusBar(30) et y indiquer à l’aide de setStatusText() le nombre d’aliens qui ont atterri dans la ville malgré la vigilance du joueur.

3.

Les aliens, une fois atterris, ne devraient pas simplement disparaître mais être remplacés par un sprite différent et immobile qui apparaît dans la cellule d’atterrissage. Le sprite "sprites/alien_1.png" fera par exemple très bien l’affaire.

Conseil: avec removeSelf(), il est possible de supprimer l’acteur pour le remplacer par un nouvel acteur créé avec addActor().

4*.

Les aliens qui sont atterris communiquent aux aliens attaquants leur position d’atterrissage de sorte que les futurs aliens ne tombent que dans les colonnes non encore occupées. Une fois que toutes les colonnes ont été conquises, le jeu se termine avec le message « Game Over ». On peut utiliser à cet effet le sprite ("sprites/gameover.gif").

(Conseil: on peut mettre le gestionnaire de jeu en pause avec la fonction doPause())

 
5*. Étendez le jeu en y ajoutant des nouveautés de votre propre cru.