EINFÜHRUNG |
In der Gamelibrary JGameGrid werden alle Spielfiguren aus der Klasse Actor abgeleitet, damit sie bereits ohne Programmieraufwand viele wichtige Eigenschaften und Fähigkeiten besitzen. Ihr spezifisches Aussehen erhalten sie aber über ihr Erscheinungsbild, das als Bilddatei, auch Sprite genannt, geladen wird. Spielfiguren sind in vieler Hinsicht animiert: Sie bewegen sich über das Spielfeld und ändern dabei ihr Erscheinungsbild, z.B. ihre Körperhaltung oder ihre Miene. Aus diesem Grund können einem Actor-Objekt beliebig viele verschiedene Spritebilder zugeordnet werden, die über einen ganzzahligen Index, der Sprite-ID, unterschieden werden. Dies ist einfacher, als Actors mit unterschiedlichen Sprites durch Klassenableitungen zu modellieren. Spielfiguren werden in der Regel auch ihren Ort, ihre Bewegungsrichtung und ihren Rotationswinkel verändern. Dabei sollte der Rotationswinkel automatisch der Bewegungsrichtung angepasst werden. In JGameGrid muss aus Effizenzgründen bereits bei der Definition der Actors angegeben werden, ob diese rotierbar und welche Spritebilder zugeordnet sind. Diese werden bei der Erstellung des Actor-Objekt in einen Bildpuffer geladen, der auch die rotierten Bilder enthält. Zu Laufzeit müssen dann die Bilder weder von der Festplatte geladen noch sonstwie transformiert werden, was zu einem Performanzverlust führen würde. Standardmässig werden 60 Spritebilder für alle 6 Grad erzeugt. In JGameGrid wird ein Animationskonzept angewendet, das man auch in anderen Game-Libraries, insbesondere in Greenfoot [mehr... Greenfoot ist ein bekanntes Programmier-Lernsystem auf der Basis von Java, das auch einen Programmeditor (BlueJ) umfasst] findet. Fundamentales Animationsprinzip:
Damit dieses geistreiche Prinzip funktioniert, müssen sich allerdings die Actors kooperativ verhalten, d.h. kurz laufenden Code aufweisen. Insbesondere wirken sich Schleifen und Delays katastrophal aus, da andere Actors dadurch auf den Aufruf ihres eigenen act() warten müssen. Das Zeichnen der Spritebilder erfolgt nach folgendem Prinzip. In der Game-Loop werden die Bilder aller Actoren in der Reihenfolge der Paint-Order-Liste in einen Bildschirmpuffer kopiert und dieser am Ende im Spielfenster gerendert. Die Reihenfolge des Durchlaufs legt damit auch die Sichtbarkeit der Spritebilder fest: Spritebilder später durchlaufener Actors überdecken die anderen, liegen also sozusagen weiter oben. Da die Actors in der Reihenfolge des Aufrufs von addActor() in die Paint-Order-Liste eingefügt werden, liegen später hinzugefügte Sprites oberhalb der anderen. Sowohl die Act-Order-Liste wie die Paint-Order-Liste lassen sich nachträglich vielseitig verändern, Zwar können bei der Initialisierung einem Actor beliebig viele Spritebilder zugeordnet werden, aber diese können zu Laufzeit nicht verändert werden. Benötigt man stark ändernde Spritebilder, z.B. einen Bildtext, so ist es möglich, den Actor erst zu Laufzeit als dynamischen Actor mit Hilfe von üblichen Grafikfunktionen zu erzeugen.
|
PFEILBOGEN BEWEGEN UND PFEILE ABSCHIESSEN |
Mit einer Armbrust, die du mit der Tastatur steuerst, willst du Pfeile abschiessen, die sich auf einer natürlichen Bahn (Wurfparabel) bewegen. Später willst du diese Pfeile verwenden, um herumfliegende Früchte zu halbieren. Du schreibst eine Klasse Crossbow, die du von der Klasse Actor ableitest. Beim Aufruf des Konstruktors der Basisklasse Actor sagst du mit True, dass es sich um einen rotierbaren Actor handelt. Der Wert 2 gibt an, dass er 2 Spritebilder besitzt, nämlich eines mit einer gespannten Armbrust und aufgesetztem Pfeil und eines für die entspannte Armbrust ohne Pfeil. Die Bilddateien werden automatisch unter dem Namen sprites/crossbow_0.gif und sprites/crossbow_1.gif gesucht und befinden sich in der Distribution von TigerJython. Actor.__init__(self, True, "sprites/crossbow.gif", 2)
Die Armbrust wird mit Tastaturevents gesteuert: Mit Cursor-up/down veränderst du die Richtung und mit der Space-Taste schiesst du den Pfeil ab. Der Callback keyCallback() ist in makeGameGrid() als keyPressed registriert. Die Pfeilklasse Dart ist bereits etwas komplizierter, müssen sich doch die Pfeile auf einer Wurfparabel in einem x-y-Koordinatensystem mit horizontaler x und nach unten zeigender y-Achse bewegen. Die Flugbahn wird nicht aus einer Kurvengleichung, sondern iterativ als Veränderung in der kurzen Zeit dt bestimmt. Aus der Kinematik ist bekannt, dass sich dabei die neuen Geschwindigkeitskoordianten (vx', vy') und die neuen Ortskoordinaten (px', py') nach der Zeit dt wie folgt berechnen (g = 9.81m/s^2 ist die Gravitationsbeschleunigung): 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()
|
MEMO |
Beim Aufruf des Konstruktors der Klasse Actor wird angegeben, ob der Actor rotierbar ist und ob ihm mehrere Spritebilder zugeordnet sind. [mehr...
Die Spritebilder werden zu diesem Zeitpunkt von der Festplatte in einen Spritebuffer Die Richtung des Pfeils drehst du ständig in Richtung der Geschwindigkeit, damit ein natürliches Flugbild entsteht. |
FRÜCHTEFABRIK UND BEWEGTE FRÜCHTE |
Dein Programm soll drei Sorten von Früchten verwenden: Melonen, Orangen und Erdbeeren. Die Früchte werden laufend in zufälliger Reihenfolge erzeugt und bewegen sich dann vom rechten oberen Bildrand mit zufällig variierter Horizontalgeschwindigkeit auf einer Wurfparabel nach links. Die drei Früchtesorten haben viele Gemeinsamkeiten und ein paar wenige Unterschiede. Es wäre daher kein guter Entscheid, die Klassen Melon, Orange und Strawberry aus Actor abzuleiten, denn dann müsstest du die Gemeinsamkeiten in jeder Klasse neu implementieren, was zu der verpönten Codeduplikation führt. Es ist angebracht, in dieser Situation eine Hilfsklasse Fruit zu definieren, in der die Gemeinsamkeiten implementiert sind und die speziellen Früchte Melon, Orange und Strawberry aus Fruit abzuleiten. Die Erzeugung der Früchte delegierst du einer Klasse, die man eine Fabrik-Klasse (factory) nennt. Obschon sie kein Spritebild besitzt, leitest du sie ebenfalls aus Actor ab, damit du act() verwenden kannst, um neue Früchte zu erzeugen. Eine Factory-Klasse hat eine spezifische Eigenschaft: Obschon sie mehrere Früchte erzeugt, gibt es davon nur eine einzige Instanz [mehr... Man nennt eine solche Klasse in der Theorie der Entwurfsmuster ein Singleton]. Es ist daher nicht üblich, den Konstruktor zu verwenden, der ja zur Erzeugung von mehreren Instanzen vorgesehen ist. Factory-Klassen besitzen darum eine Methode create() (oder mit einem ähnlich vielsagenden Namen), die ein einziges Objekt der Klasse erstellt und es als Funktionswert zurückgibt. Jeder weitere Aufruf von create() liefert dann nur die bereits erzeugte Factory-Instanz [mehr...Eine solche Klasse nennt man ein Singleton].
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()
|
MEMO |
In einer statischen Methode steht der Parameter self nicht zur Verfügung. Daher müssen alle Variablen, die in create() zugewiesen werden, statische Variablen sein (Voranstellen des Klassennamens) [mehr...
Die Erstellung einer Instanz von FruitFactory mit dem Konstruktor sollte verboten werden. In einer Entwicklungsphase können gewisse Funktionen oder Methoden noch unvollständig codiert sein. Man kann beispielsweise lediglich in die Konsole ausschreiben, dass sie aufgerufen wurden. Du machst davon mit print "Factory expired" Gebrauch. Beim Hinzufügen eines Actors ins GameGrid mit addActor() wird der Bildpuffer automatisch auf den Bildschirm gerendert, damit der Actor sofort sichtbar ist. Falls der Simulationszyklus gestartet ist, wird das Rendern sowieso in jedem Zyklus ausgeführt. Darum sollte in diesem Fall besser addActorNoRefresh() verwendet werden, denn zu häufiges Rendern kann zu Bildschirmflackern führen. |
ZUSAMMENBAU UND KOLLISIONEN BEHANDELN |
Die beiden eben geschriebenen Programmteile könnten auch von zwei Arbeitsgruppen entwickelt worden sein. Die nächste Aufgabe besteht darin, diese Teile zusammenführen, was nicht immer ganz leicht ist. Ist aber wie hier der Programmstil einheitlich und der Code weitgehend entkoppelt, so wird dadurch das "Mergen" des Codes wesentlich erleichtert Als neue Funktionalität soll zudem das Halbieren der Früchte beim Zusammentreffen mit einem Pfeil eingebaut werden. Wir haben dies bereits vorbereitet, da ja die Früchte zwei Spritebilder haben, eines für die ganze und eines für die halbierte Frucht. Wie du weisst, werden Kollisionen zwischen Actors durch einen Kollisionsevent erfasst. Dazu legst du für jeden Actor fest, welche die möglichen Kollisionspartner sind. Überlege dir dazu das Folgende: Erzeugst du beim Abschiessen einen Pfeil, so sind alle gegenwärtig vorhandenen Früchte Kollisionspartner. Vergiss aber nicht, dass während der Bewegung des Pfeils auch neue Früchte hinzukommen. Darum musst du beim Erzeugen einer Frucht auch alle vorhandenen Pfeile (vielleicht gibt es nur einen) als Kollisionspartner festlegen. In JGameGrid kannst du addCollisionActors() eine ganze Liste von Actoren als Kollisionspartner übergeben. Mit getActors(Klasse) kriegst du eine Liste mit allen Actoren der angegeben Klasse, die du übergeben kannst.
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()
|
MEMO |
Hast du mit addCollisionActor() oder addCollisionActors() die Kollisionspartner deines Actors angegeben, so musst du in der Klasse des Actors die Methode collide() einfügen, die bei jeder Kollision automatisch aufgerufen wird. Der Rückgabewert muss ein Integer sein, der sagt, wie manchen Simulationszyklus die Kollisionen inaktiv sind (hier 0 sein). Eine Zahl grösser als 0 ist manchmal nötig, damit sich die beiden Partner wieder voneinander entfernen, bevor Kollisionen aktiv werden. Als Kollisionsgebiete sind standardmässig die umgebenden Rechtecke des Spritebildes aktiv (Sie werden natürlich bei der Rotation der Aktoren mitgedreht). Für den Pfeil könntest du auch mit setCollisionCircle(Point(20, 0), 10) einen Kreis an der Pfeilspitze als Kollisionsgebiet festlegen, damit Früchte, die mit dem hinteren Teil des Pfeils kollidieren, nicht halbiert werden. |
SPIELZUSTAND ANZEIGEN UND GAME-OVER BEHANDELN |
Zum Dessert verfeinerst du den Code noch, indem du einen Game-Score und Benutzerinformation einbaust. Am einfachsten schreibst du sie in einer Statusbar aus. Wie du bereits weisst, ist es günstig, im Hauptteil des Programms einen Game-Supervisor zu implementieren. Dieser soll die Anzahl der getroffenen und verpassten Früchte ausschreiben und das Spiel beenden, wenn die Früchtefabrik ihre Kapazität erreicht ist. Er zeigt also den Endstand an, erzeugt einen Game-Over-Actor und verhindert das Weiterspielen. 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)
|
MEMO |
Bei Game-Over sollten die meisten Benutzeraktionen verboten werden. Am einfachsten ist es, ein Flag isGameOver = True einzuführen, mit dem man die Aktionen durch vorzeitiges return in den betreffenden Funktionen und Methoden unterbindet. Es soll möglich bleiben, auch bei Game-Over die Armbrust zu bewegen, aber verboten sein, damit zu schiessen. |
AUFGABEN |
|