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.
|
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.
from gamegrid import * makeGameGrid(10, 10, 60, Color.red, "sprites/town.jpg", False) show()
|
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.
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() |
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 |
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) |
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) |
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 |
|