INTRODUCTION |
Lorsque l’on travaille avec la bibliothèque JGameGrid, tous les acteurs devraient être dérivés de la classe Actor pour qu’ils réutilisent automatiquement de nombreuses fonctionnalités et capacités incluses cette dernière sans aucun effort de programmation. Leur apparence propre doit cependant être chargée depuis un fichier image spécifique appelé sprite. Les personnages de jeu sont animés de différentes manières : ils bougent à travers la surface de jeu en changeant parfois d’apparence, de posture ou d’expression. De ce fait, on peut attribuer à un objet acteur de nombreux sprites différents que l’on distingue par un indice entier, l’ID du sprite. Cette technique est plus simple que de spécialiser les différentes apparences sous forme de classes dérivées. Les personnages de jeu modifient donc souvent leur position, direction de mouvement et angle de rotation. L’orientation du sprite devrait être automatiquement ajustée à la direction de son mouvement. Avec JGameGrid, il faut spécifier dès la création de l’acteur, pour des raisons de performance, si ce dernier peut subir des rotations et, le cas échéant, quels sont les sprites correspondant aux différentes orientations possibles. Ces derniers sont, dès l’instanciation de la classe Actor, chargés dans une mémoire tampon qui contient également les différentes rotations des images. Ceci permet d’éviter de devoir, lors de l’exécution du jeu, charger les images depuis le disque dur ou de leur appliquer des transformations, ce qui dégraderait les performances. Par défaut, pour chaque sprite chargé depuis une image, 60 images de sprites sont générées pour des angles de rotations entre 0° et 360°, par incréments de 6°. JGameGrid fait usage d’un concept d’animation également disponible dans d’autres bibliothèques de développement de jeu, notamment Greenfoot [plus... Greenfoot est un système d’apprentissage de la programmation basé sur Java qui inclut également un éditeur de code, BlueJ] . Principe fondamental de l’animation:
Pour que ce principe astucieux fonctionne correctement, il est nécessaire que les acteurs adoptent un comportement coopératif : leur méthode act() doit se contenter d’exécuter un code qui retourne très rapidement. L’insertion de boucles ou de délais dans la méthode act(), ne serait-ce que d’un seul acteur, entraîne des effets catastrophiques puisque les autres acteurs devront ainsi attendre les uns sur les autres pour voir leur méthode act() être traitée. Il faut avoir conscience du mécanisme qui régit le rendu des images de sprite des acteurs présents dans le jeu. Dans la boucle de jeu, les sprites de chaque acteur sont copiés dans une mémoire tampon regroupant les images de tous les acteurs selon l’ordre spécifié par la liste paintOrder. C’est à partir de cette mémoire tampon que le rendu des acteurs est effectué dans la fenêtre de jeu. L’ordre de rendu des acteurs détermine donc leur visibilité : le sprite des derniers acteurs dessinés va se situer au premier plan tandis que celui des premiers acteurs sera en arrière-plan. Du fait que les acteurs sont insérés dans la liste paintOrder selon l’ordre d’ajout avec addActor(), les acteurs ajoutés en dernier apparaîtront au premier plan. Heureusement, autant l’ordre de la liste actOrder que celui de paintOrder peuvent être modifiés lors de l’exécution. En particulier, un acteur peut demander à être placé en tête de liste avec setOnTop() pour que son sprite apparaisse au premier plan et que sa méthode act() soit invoquée en prioritéBien qu’il soit possible d’assigner un nombre arbitraire de sprites à un acteur lors de sa création, il n’est ensuite plus possible de les modifier lors de l’exécution. Si un acteur (par exemple un texte de légende) doit changer d’apparence dynamiquement lors de l’exécution du jeu, il est possible de générer l’acteur dynamiquement grâce aux fonctions graphiques habituelles.
|
BOUGER UNE ARBALÈTE ET UNE FLÈCHE |
Développons un jeu dans lequel le joueur bouge, à l’aide des flèches directionnelles, une arbalète (crossbow) qui tire des flèches (arrows) empruntant une trajectoire naturelle (parabole). Les flèches devront atteindre des fruits volant pour les pourfendre. On définit une classe Crossbow qui dérive de la classe Actor. Pour indiquer qu’il s’agit d’un acteur pouvant subir des rotations, on indique True lors de l’appel du constructeur de la classe de base. Le troisième paramètre, en l’occurrence l’entier 2, indique qu’il y a deux images de sprite associées avec l’arbalète, l’une représentant l’arbalète tendue et chargée d’une flèche et l’autre représentant l’arbalète au repos et dépourvue de flèche. Les images correspondantes sont automatiquement cherchées sous le nom sprites/crossbow_0.gif et sprites/crossbow_1.gif dans la distribution de TigerJython. Actor.__init__(self, True, "sprites/crossbow.gif", 2)
L’arbalète est contrôlée par des événements clavier : on change son orientation avec les touches haut/bas et on la déclenche avec la touche espace. La fonction de rappel keyCallback() qui sert de gestionnaire d’événements est enregistrée lors de l’appel à makeGameGrid() avec le paramètre nommé keyPressed. La classe Dart modélisant les flèches est un peu compliquée du fait que ces dernières doivent se mouvoir sur une trajectoire parabolique dans un système de coordonnées xOy dont l’axe Ox est horizontal vers la droite et l’axe Oy vertical et orienté vers le bas. La trajectoire n’est pas déterminée par une équation de courbe mais plutôt parcourue itérativement par de petits changements temporels dt. On sait de la cinématique que les nouvelles composantes du vecteur vitesse (vx', vy') et du vecteur position (px', py') sont déterminées après chaque incrément temporel dt comme suit, où g = 9.81m/s^2 est l’accélération de la pesanteur à la surface de la terre : vx' = vx px'= px + vx * dt
from gamegrid import * import math # ------------------- class Crossbow ----------------------- class Crossbow(Actor): def __init__(self): Actor.__init__(self, True, "sprites/crossbow.gif", 2) # ------ class Dart ---------------- class Dart(Actor): def __init__(self, speed): Actor.__init__(self, True, "sprites/dart.gif") self.speed = speed self.dt = 0.005 * getSimulationPeriod() # Called when actor is added to GameGrid def reset(self): self.px = self.getX() self.py = self.getY() self.vx = self.speed * math.cos(math.radians(self.getDirection())) self.vy = self.speed * math.sin(math.radians(self.getDirection())) def act(self): self.vy = self.vy + g * self.dt self.px = self.px + self.vx * self.dt self.py = self.py + self.vy * self.dt self.setLocation(Location(int(self.px), int(self.py))) self.setDirection(math.degrees(math.atan2(self.vy, self.vx))) if not self.isInGrid(): self.removeSelf() crossbow.show(0) # Load crossbow # ------ End of class definitions -------------------- def keyCallback(e): code = e.getKeyCode() if code == KeyEvent.VK_UP: crossbow.setDirection(crossbow.getDirection() - 5) elif code == KeyEvent.VK_DOWN: crossbow.setDirection(crossbow.getDirection() + 5) elif code == KeyEvent.VK_SPACE: if crossbow.getIdVisible() == 1: # Wait until crossbow is loaded return crossbow.show(1) # crossbow is released dart = Dart(100) addActorNoRefresh(dart, crossbow.getLocation(), crossbow.getDirection()) screenWidth = 600 screenHeight = 400 g = 9.81 makeGameGrid(screenWidth, screenHeight, 1, False, keyPressed = keyCallback) setTitle("Use Cursor up/down to target, Space to shoot.") setBgColor(makeColor("skyblue")) crossbow = Crossbow() addActor(crossbow, Location(80, 320)) setSimulationPeriod(30) doRun() show()
|
MEMENTO |
Lors de l’appel du constructeur de la classe Actor, on peut indiquer si l’acteur va subir des rotations et s’il faut lui associer plusieurs images de sprite. Les images de sprite sont chargées à ce moment depuis le disque et placées dans une mémoire tampon qui contiendra également toutes les différentes rotations des images. On règle sans cesse l’orientation de la flèche pour qu’elle corresponde à la direction de son vecteur vitesse dans le but de rendre son vol plus naturel. |
FABRIQUE DE FRUITS ET FRUITS EN MOUVEMENT |
Notre programme va utiliser trois différents types de fruits : des melons, des oranges et des fraises. Ceux-ci sont générés en continu de manière aléatoire et se déplacent depuis le coin supérieur droit de la fenêtre vers la gauche avec une vitesse horizontale initiale aléatoire et suivant une trajectoire parabolique. Ces trois différents types de fruits partagent de nombreuses caractéristiques communes et ne comportent que quelques différences mineures. Il ne serait de ce fait pas une bonne idée de dériver ces trois classes directement de la classe de base Actor car cela impliquerait de coder les méthodes qui leur seraient communes dans chacune des classes, ce qui constituerait une redondance de code à éviter à tout prix. Dans cette situation, il est plus judicieux de définir une classe auxiliaire et intermédiaire Fruit dans laquelle sont implémentées les fonctionnalités communes et de laquelle les classes spécifiques Melon, Orange et Strawberry peuvent être dérivées. On délègue la création d’un fruit à la classe FruitFactory d’un genre particulier : il s’agit d’une classe fabrique. Bien que celle-ci ne possède pas d’image de sprite, on peut néanmoins la dériver de la classe Actor de telle sorte que sa méthode act() puisse être utilisée pour générer de nouveaux fruits. Cette classe fabrique a une particularité : bien qu’elle produise de nombreux fruits, il n’y aura qu’une seule instance de cette classe dans tout le jeu [plus...Une telle classe est appelée Singleton]. De ce fait, on ne définit généralement pas de constructeur pour une telle classe puisqu’elle n’est pas destinée à engendrer plusieurs instances. On définit plutôt une méthode nommée create() ou de manière similaire qui crée une unique instance de la classe et la retourne comme valeur de retour. Tous les appels subséquents à cette méthode create(), au lieu de recréer une nouvelle instance, ne vont faire que de renvoyer la référence à l’instance précédemment créée.
from gamegrid import * from random import randint, random # ---------- class Fruit ------------------------ class Fruit(Actor): def __init__(self, spriteImg, vx): Actor.__init__(self, True, spriteImg, 2) # rotatable, 2 sprites self.vx = vx self.vy = 0 def reset(self): # Called when Fruit is added to GameGrid self.px = self.getX() self.py = self.getY() def act(self): self.movePhysically() self.turn(10) def movePhysically(self): self.dt = 0.002 * getSimulationPeriod() self.vy = self.vy + g * self.dt # vx = const self.px = self.px + self.vx * self.dt self.py = self.py + self.vy * self.dt self.setLocation(Location(int(self.px), int(self.py))) self.cleanUp() def cleanUp(self): if not self.isInGrid(): self.removeSelf() # ------ class Melon ----------- class Melon(Fruit): def __init__(self, vx): Fruit.__init__(self, "sprites/melon.gif", vx) # ------ class Orange ----------- class Orange(Fruit): def __init__(self, vx): Fruit.__init__(self, "sprites/orange.gif", vx) # ------ class Strawberry ----------- class Strawberry(Fruit): def __init__(self, vx): Fruit.__init__(self, "sprites/strawberry.gif", vx) # ------------------- class FruitFactory ------------------- class FruitFactory(Actor): myFruitFactory = None myCapacity = 0 nbGenerated = 0 @staticmethod def create(capacity, slowDown): if FruitFactory.myFruitFactory == None: FruitFactory.myCapacity = capacity FruitFactory.myFruitFactory = FruitFactory() FruitFactory.myFruitFactory.setSlowDown(slowDown) # slows down act() call for this actor return FruitFactory.myFruitFactory def act(self): if FruitFactory.nbGenerated == FruitFactory.myCapacity: print("Factory expired") return vx = -(random() * 20 + 30) r = randint(0, 2) if r == 0: fruit = Melon(vx) elif r == 1: fruit = Orange(vx) else: fruit = Strawberry(vx) FruitFactory.nbGenerated += 1 y = int(random() * screenHeight / 2) addActorNoRefresh(fruit, Location(screenWidth-50, y), 180) # ------ End of class definitions -------------------- FACTORY_CAPACITY = 20 FACTORY_SLOWDOWN = 35 screenWidth = 600 screenHeight = 400 g = 9.81 makeGameGrid(screenWidth, screenHeight, 1, False) setTitle("Use Cursor up/down to target, Space to shoot.") setBgColor(makeColor("skyblue")) factory = FruitFactory.create(FACTORY_CAPACITY, FACTORY_SLOWDOWN) addActor(factory, Location(0, 0)) # needed to run act() setSimulationPeriod(30) doRun() show()
|
MEMENTO |
Dans une méthode statique, il n’y a pas de paramètre self. De ce fait, les variables créées au sein de la méthode create() doivent être des variables statiques (leur nom doit être préfixé du nom de la classe) [plus...L’instanciation de la classe FruitFactory devrait être interdite. Lors de l’ajout d’acteurs dans la grille de jeu avec addActor(), les images présentes dans la mémoire tampon sont automatiquement affichées à l’écran de sorte que l’acteur est immédiatement visible. Dès que le cycle de simulation est démarré, le rendu est de toute manière effectué à chaque cycle de simulation. C’est la raison pour laquelle, dans le cas présent, il faut utiliser addActorNoRefresh() pour éviter qu’un rendu trop fréquent n’entraine des scintillements à l’écran. |
GESTION ET INTÉGRATION DES COLLISIONS |
Les deux parties du programme que nous venons de mettre en place auraient pu être développées indépendamment par deux équipes de développeurs distincts. Si le style de programmation est cohérent et en majorité découplé à l’exemple de notre code, il est presque un jeu d’enfant de fusionner les deux parties. Nous allons donc mettre ces deux pièces ensemble et ajouter au passage une nouvelle fonctionnalité permettant aux fruits d’être fendus lorsqu’ils sont atteints par la flèche. Le terrain est déjà préparé puisque les fruits disposent de deux images de sprite : l’une pour le fruit entier et l’autre pour le fruit fendu. Comme vous le savez, les collisions entre acteurs sont détectées par des événements de collision et leur gestion nécessite d’identifier, pour chaque acteur, les partenaires de collision potentiels. En ce qui concerne la flèche, il s’agit de tous les fruits existants et visibles à l’écran. Il ne faut cependant pas oublier que davantage de fruits sont ajoutés à la grille de jeu pendant le vol de la flèche. Ceci exige de déclarer également toutes les flèches (même s’il n’y en a probablement qu’une seule) comme partenaires de collision lors de la création d’un fruit. Avec JGameGrid, il est également possible de passer toute une liste d’acteurs à la fonction addCollisionActors() en tant que partenaires de collision. La fonction getActors(class) permet d’obtenir une liste de tous les acteurs appartenant à la classe mentionnée que l’on peut passer à addCollisionActors().
from gamegrid import * from random import randint, random import math # ---------- class Fruit ------------------------ class Fruit(Actor): def __init__(self, spriteImg, vx): Actor.__init__(self, True, spriteImg, 2) self.vx = vx self.vy = 0 self.isSliced = False def reset(self): # Called when Fruit is added to GameGrid self.px = self.getX() self.py = self.getY() def act(self): self.movePhysically() self.turn(10) def movePhysically(self): self.dt = 0.002 * getSimulationPeriod() self.vy = self.vy + g * self.dt self.px = self.px + self.vx * self.dt self.py = self.py + self.vy * self.dt self.setLocation(Location(int(self.px), int(self.py))) self.cleanUp() def cleanUp(self): if not self.isInGrid(): self.removeSelf() def sliceFruit(self): if not self.isSliced: self.isSliced = True self.show(1) def collide(self, actor1, actor2): actor1.sliceFruit() return 0 # ------ class Melon ----------- class Melon(Fruit): def __init__(self, vx): Fruit.__init__(self, "sprites/melon.gif", vx) # ------ class Orange ----------- class Orange(Fruit): def __init__(self, vx): Fruit.__init__(self, "sprites/orange.gif", vx) # ------ class Strawberry ----------- class Strawberry(Fruit): def __init__(self, vx): Fruit.__init__(self, "sprites/strawberry.gif", vx) # ------------------- class FruitFactory ------------------- class FruitFactory(Actor): myCapacity = 0 myFruitFactory = None nbGenerated = 0 @staticmethod def create(capacity, slowDown): if FruitFactory.myFruitFactory == None: FruitFactory.myCapacity = capacity FruitFactory.myFruitFactory = FruitFactory() FruitFactory.myFruitFactory.setSlowDown(slowDown) return FruitFactory.myFruitFactory def act(self): self.createRandomFruit() def createRandomFruit(self): if FruitFactory.nbGenerated == FruitFactory.myCapacity: print("Factory expired") return vx = -(random() * 20 + 30) r = randint(0, 2) if r == 0: fruit = Melon(vx) elif r == 1: fruit = Orange(vx) else: fruit = Strawberry(vx) FruitFactory.nbGenerated += 1 y = int(random() * screenHeight / 2) addActorNoRefresh(fruit, Location(screenWidth-50, y), 180) # for a new fruit, the collision partners are all existing darts fruit.addCollisionActors(toArrayList(getActors(Dart))) # ------------------- class Crossbow ----------------------- class Crossbow(Actor): def __init__(self): Actor.__init__(self, True, "sprites/crossbow.gif", 2) # ------ class Dart ---------------- class Dart(Actor): def __init__(self, speed): Actor.__init__(self, True, "sprites/dart.gif") self.speed = speed self.dt = 0.005 * getSimulationPeriod() # Called when actor is added to GameGrid def reset(self): self.px = self.getX() self.py = self.getY() dx = math.cos(math.radians(self.getDirectionStart())) self.vx = self.speed * dx dy = math.sin(math.radians(self.getDirectionStart())) self.vy = self.speed * dy def act(self): self.vy = self.vy + g * self.dt self.px = self.px + self.vx * self.dt self.py = self.py + self.vy * self.dt self.setLocation(Location(int(self.px), int(self.py))) self.setDirection(math.degrees(math.atan2(self.vy, self.vx))) if not self.isInGrid(): self.removeSelf() crossbow.show(0) # Load crossbow def collide(self, actor1, actor2): actor2.sliceFruit() return 0 # ------ End of class definitions -------------------- def keyCallback(e): code = e.getKeyCode() if code == KeyEvent.VK_UP: crossbow.setDirection(crossbow.getDirection() - 5) elif code == KeyEvent.VK_DOWN: crossbow.setDirection(crossbow.getDirection() + 5) elif code == KeyEvent.VK_SPACE: if crossbow.getIdVisible() == 1: # Wait until crossbow is loaded return crossbow.show(1) # crossbow is released dart = Dart(100) addActorNoRefresh(dart, crossbow.getLocation(), crossbow.getDirection()) # for a new dart, the collision partners are all existing fruits dart.addCollisionActors(toArrayList(getActors(Fruit))) FACTORY_CAPACITY = 20 FACTORY_SLOWDOWN = 35 screenWidth = 600 screenHeight = 400 g = 9.81 makeGameGrid(screenWidth, screenHeight, 1, False, keyPressed = keyCallback) setTitle("Use Cursor up/down to target, Space to shoot.") setBgColor(makeColor("skyblue")) factory = FruitFactory.create(FACTORY_CAPACITY, FACTORY_SLOWDOWN) addActor(factory, Location(0, 0)) # needed to run act() crossbow = Crossbow() addActor(crossbow, Location(80, 320)) setSimulationPeriod(30) doRun() show()
|
MEMENTO |
Une fois que les partenaires de collision d’un acteur sont déclarés, il faut redéfinir la méthode collide() dans la classe de cet acteur pour qu’elle soit appelée lors de chaque collision. La valeur de retour doit être un nombre entier indiquant pendant combien de cycles de simulation, la détection de collision doit être suspendue (en l’occurrence 0). Un nombre supérieur à 0 est parfois nécessaire pour que les deux partenaires aient le temps de se séparer avant qu’une nouvelle collision soit détectée. Notez bien que dans la définition de la méthode collide(self, actor1, actor2), actor1 est l’acteur de la classe dans laquelle collide() est définie. Par défaut, la zone de détection de collisions correspond au rectangle circonscrit à l’image du sprite. Ce rectangle est évidemment tourné avec l’image lors des rotations. On pourrait définir la zone de collision des flèches comme un cercle de faible rayon autour de leur pointe, pour éviter que les fruits ne soient fendus lors d’une collision avec la queue d’une flèche. setCollisionCircle(Point(20, 0), 10) |
AFFICHAGE DE L’ÉTAT DU JEU ET GESTION DE LA FIN DU JEU |
Pour le dessert, raffinons encore notre jeu en y ajoutant des informations à destination de l’utilisateur ainsi que le décompte des points. Le plus simple est de les écrire dans la barre d’état de la fenêtre de jeu. Comme nous l’avons déjà vu, il est judicieux d’implémenter les règles du jeu dans un gestionnaire de jeu indépendant des acteurs dans le programme principal. Ce dernier affiche le nombre de fruits touchés, le nombre de ceux qui ont été manqués et termine le jeu lorsque la fabrique de fruits est épuisée. Le gestionnaire affiche également le score final, génère un acteur textuel Game Over et empêche le jeu de se poursuivre. from gamegrid import * from random import random, choice import math # ---------- class Fruit ------------------------ class Fruit(Actor): def __init__(self, spriteImg, vx): Actor.__init__(self, True, spriteImg, 2) self.vx = vx self.vy = 0 self.isSliced = False def reset(self): # Called when Fruit is added to GameGrid self.px = self.getX() self.py = self.getY() def act(self): self.movePhysically() self.turn(10) def movePhysically(self): self.dt = 0.002 * getSimulationPeriod() self.vy = self.vy + g * self.dt self.px = self.px + self.vx * self.dt self.py = self.py + self.vy * self.dt self.setLocation(Location(int(self.px), int(self.py))) self.cleanUp() def cleanUp(self): if not self.isInGrid(): if not self.isSliced: FruitFactory.nbMissed += 1 self.removeSelf() def sliceFruit(self): if not self.isSliced: self.isSliced = True self.show(1) FruitFactory.nbHit += 1 def collide(self, actor1, actor2): actor1.sliceFruit() return 0 # ------ class Melon ----------- class Melon(Fruit): def __init__(self, vx): Fruit.__init__(self, "sprites/melon.gif", vx) # ------ class Orange ----------- class Orange(Fruit): def __init__(self, vx): Fruit.__init__(self, "sprites/orange.gif", vx) # ------ class Strawberry ----------- class Strawberry(Fruit): def __init__(self, vx): Fruit.__init__(self, "sprites/strawberry.gif", vx) # ------------------- class FruitFactory ------------------- class FruitFactory(Actor): myCapacity = 0 myFruitFactory = None nbGenerated = 0 nbMissed = 0 nbHit = 0 @staticmethod def create(capacity, slowDown): if FruitFactory.myFruitFactory == None: FruitFactory.myCapacity = capacity FruitFactory.myFruitFactory = FruitFactory() FruitFactory.myFruitFactory.setSlowDown(slowDown) return FruitFactory.myFruitFactory def act(self): self.createRandomFruit() @staticmethod def createRandomFruit(): if FruitFactory.nbGenerated == FruitFactory.myCapacity: return vx = -(random() * 20 + 30) fruitClass = choice([Melon, Orange, Strawberry]) fruit = fruitClass(vx) FruitFactory.nbGenerated += 1 y = int(random() * screenHeight / 2) addActorNoRefresh(fruit, Location(screenWidth-50, y), 180) # for a new fruit, the collision partners are all existing darts fruit.addCollisionActors(toArrayList(getActors(Dart))) print(type(getActors(Dart))) # ------------------- class Crossbow ----------------------- class Crossbow(Actor): def __init__(self): Actor.__init__(self, True, "sprites/crossbow.gif", 2) # ------ class Dart ---------------- class Dart(Actor): def __init__(self, speed): Actor.__init__(self, True, "sprites/dart.gif") self.speed = speed self.dt = 0.005 * getSimulationPeriod() # Called when actor is added to GameGrid def reset(self): self.px = self.getX() self.py = self.getY() dx = math.cos(math.radians(self.getDirectionStart())) self.vx = self.speed * dx dy = math.sin(math.radians(self.getDirectionStart())) self.vy = self.speed * dy def act(self): if isGameOver: return self.vy = self.vy + g * self.dt self.px = self.px + self.vx * self.dt self.py = self.py + self.vy * self.dt self.setLocation(Location(int(self.px), int(self.py))) self.setDirection(math.degrees(math.atan2(self.vy, self.vx))) if not self.isInGrid(): self.removeSelf() crossbow.show(0) # Load crossbow def collide(self, actor1, actor2): actor2.sliceFruit() return 0 # ------ End of class definitions -------------------- def keyCallback(e): code = e.getKeyCode() if code == KeyEvent.VK_UP: crossbow.setDirection(crossbow.getDirection() - 5) elif code == KeyEvent.VK_DOWN: crossbow.setDirection(crossbow.getDirection() + 5) elif code == KeyEvent.VK_SPACE: if isGameOver: return if crossbow.getIdVisible() == 1: # Wait until crossbow is loaded return crossbow.show(1) # crossbow is released dart = Dart(100) addActorNoRefresh(dart, crossbow.getLocation(), crossbow.getDirection()) # for a new dart, the collision partners are all existing fruits dart.addCollisionActors(toArrayList(getActors(Fruit))) FACTORY_CAPACITY = 20 FACTORY_SLOWDOWN = 35 screenWidth = 600 screenHeight = 400 g = 9.81 isGameOver = False makeGameGrid(screenWidth, screenHeight, 1, False, keyPressed = keyCallback) setTitle("Use Cursor up/down to target, Space to shoot.") setBgColor(makeColor("skyblue")) addStatusBar(30) factory = FruitFactory.create(FACTORY_CAPACITY, FACTORY_SLOWDOWN) addActor(factory, Location(0, 0)) # needed to run act() crossbow = Crossbow() addActor(crossbow, Location(80, 320)) setSimulationPeriod(30) doRun() show() while not isDisposed() and not isGameOver: # Don't show message if same oldMsg = "" msg = "#hit: "+str(FruitFactory.nbHit)+" #missed: "+str(FruitFactory.nbMissed) if msg != oldMsg: setStatusText(msg) oldMsg = msg if FruitFactory.nbHit + FruitFactory.nbMissed == FACTORY_CAPACITY: isGameOver = True removeActors(Dart) setStatusText("You smashed " + str(FruitFactory.nbHit) + " out of " + str(FACTORY_CAPACITY) + " fruits") addActor(Actor("sprites/gameover.gif"), Location(300, 200)) delay(100)
|
MEMENTO |
La plupart des actions utilisateur devraient être interdites après le Game Over. Le plus simple pour implémenter ces restrictions est d’introduire un fanion global isGameOver = True utilisé pour effectuer un return conditionnel prématuré dans le gestionnaire des événements clavier et dans la méthode act() de la flèche. L’utilisateur devrait par contre toujours être en mesure de bouger l’arbalète après le Game Over mais pas de tirer des flèches. |
EXERCISES |
|