image image


image

Jedes Spiel braucht Gegner: Auftritt der Killertomaten (Happy Halloween Stage 4)

Langsam nimmt mein kleines, in TigerJython entwickeltes Halloween-Spiel Gestalt an. Eines fehlte allerdings noch: Richtige Gegner, die dem Spieler gefährlich werden können, denn die abzuschießenden Kürbisköpfe dienen eigentlich nur der Punktebeschaffung. Also habe ich einen altbekannten und hochgefährlichen Akteur aus der Mottenkiste der Fimgeschichte hervorgekramt und präsentiere nun stolz den »Angriff der Killertomaten«.

Da diese sich zu einem großen Teil exakt so verhalten, wie die Kürbisse, habe ich die Klasse Enemy() als Oberklasse verwendet und zwei Unterklassen erzeugt, die jeweils von Enemy() erben, die Klassen Pumpkin() und Tomato():

class Enemy(Actor):
    
    def __init__(self):
        Actor.__init__(self, False, os.path.join(DATAPATH, "alien.png"))
        self.speed = randint(1, 5)
        
    def act(self):
        self.move(self.speed)
        if self.getX() >= RIGHTMARGIN:
            self.setX(RIGHTMARGIN)
            self.direction -= randint(90, 270)
        elif self.getX() <= LEFTMARGIN:
            self.setX(LEFTMARGIN)
            self.direction -= randint(90, 270)
        if self.getY() >= BOTTOMMARGIN:
            self.setY(BOTTOMMARGIN)
            self.direction -= randint(90, 270)
        elif self.getY() <= TOPMARGIN:
            self.setY(TOPMARGIN)
            self.direction -= randint(90, 270)

class Pumpkin(Enemy):
    
    def __init__(self):
        Actor.__init__(self, False, os.path.join(DATAPATH, "pumpkin.png"))
        self.speed = randint(1, 5)

class Tomato(Enemy):
    
    def __init__(self):
        Actor.__init__(self, False, os.path.join(DATAPATH, "tomato.png"))
        self.speed = randint(1, 5)

Das Bild alien.png ist ein Dummy für die Klasse Enemy(), das nicht zum Einsatz kommt. Es entstammt wie die Bilder pumpkin.png und tomato.png aus den Twitter-Twemojis, die ich entsprechend zurechtgestutzt hatte.

Die Killertomaten benötigen zwei unterschiedliche Kollisionsbehandlungen: Einmal können sie natürlich wie die Kürbisse mit Raketen abgeschossen werden, daher wird diese Kollision in der Klasse Missile() abgehandelt und unterscheidet sich erst einmal nicht von der Kollision der Kürbisse mit einem Geschoß. Dann allerdings soll die Tomate nach einem Abschuß nicht einfach verschwinden, sondern an zufälliger Stelle im Spielefenster wieder auftauchen (das ist das, was aus einer Salat- eine Killertomate macht):

        if actor2.__class__.__name__ == "Tomato":
            t = Tomato()
            win.addActor(t, Location(randint(54, 640 - 14), (randint(54, 640 - 14))))
            t.setDirection(randint(20, 340))

Zur Identifizierung des abgeschossenen Gegners als Tomate benutzt GameGrid ein ziemlich abgefahrenes Konstrukt: if actor2.__class__.__name__ == "Tomato": gibt nämlich nur dann True zurück, wenn der actor wirklich aus der Unterklasse Tomato ist. Dann wird eine neue Killertomate erzeugt, sie auf eine zufällige Stelle im Spielefenster platziert und mit einer zufälligen Bewegungsrichtung versehen.

Die Kollision einer Tomate mit dem Spieler habe ich dagegen in die Spielerklasse verlegt:

    def collide(self, actor1, actor2):
        win.removeActor(self)
        win.removeActor(actor2)
        hit = Explosion()
        win.addActor(hit, Location(self.getX(), self.getY()))
        win.doPause()
        msgDlg("GAME OVER")
        return 0

Auch sie löst noch eine Explosion aus, um dann das Spiel zu beenden. Später soll noch ein Game Over Screen das Spielende anzeigen, momentan wird stattdessen aber einfach mit msgDlg() ein Dialogfenster erzeugt, das das Spielende signalisiert.

Da der Player nun auf die Klassen Tomato() und Explosion() Bezug nimmt, mußte ich seine Initialisierung im Hauptprogramm vorziehen. Er wird nun als erstes vor allen anderen Objekten instanziert.

Das waren aber die einigen Änderungen, die ich für diesen Stage vorgenommen habe. Wie immer gibt es nun das komplette Programm in seiner ganzen Schönheit zum Nachvollziehen und Nachprogrammieren:

# title: Pumpkin Attack Stage 4 – The Attack of the Killer Tomatoes
# Author: Jörg Kantel

from gamegrid import *
from random import randint
import os

WIDTH = 680
HEIGHT = 680
LEFTMARGIN = 40 + 14
RIGHTMARGIN = 640 - 14
TOPMARGIN = 40 + 14
BOTTOMMARGIN = 640 - 14

NOPUMP = 35
NOTOMATOS = 5

DATAPATH = os.path.join(os.getcwd(), "data")
BGCOLOR = Color(color("#2b3e50"))

class Background(Actor):
    
    def __init__(self):
        Actor.__init__(self, False, os.path.join(DATAPATH, "moonnight2.jpg"))
        
class Player(Actor):
    
    def __init__(self):
        Actor.__init__(self, True, os.path.join(DATAPATH, "rocket1.png"))
        self.dir = 0
        self.speed = 5
        self.firecount = 0
        self.score = 0
        c = Color.white
        displayScore = TextActor("Score: " + str(self.score), c, BGCOLOR, Font("ComicHelvetic_Medium", Font.BOLD, 20))
        win.addActor(displayScore, Location(40, 20))
        
    def fire(self):
        if self.firecount < 0:
            missile = Missile()
            enemyList = win.getActors(Enemy)
            for enemy in enemyList:
                missile.addCollisionActor(enemy)
            win.addActor(missile, Location(self.getX(), self.getY()))
            missile.direction = self.dir
            self.firecount = 15
    
    def collide(self, actor1, actor2):
        win.removeActor(self)
        win.removeActor(actor2)
        hit = Explosion()
        win.addActor(hit, Location(self.getX(), self.getY()))
        # print("GAME OVER")
        win.doPause()
        msgDlg("GAME OVER")
        return 0
                        
    def act(self):
        if isKeyPressed(37):       # LEFT
            self.dir -= 5 % 360
            self.direction = self.dir
        elif isKeyPressed(39):     # RIGHT
            self.dir += 5 % 360
            self.direction = self.dir
        elif isKeyPressed(38):     # UP
            self.move(self.speed)
            if self.getX() >= RIGHTMARGIN - 10:
                self.setX(RIGHTMARGIN -10)
                newDir = 180 - self.dir
                self.setDirection(newDir)
                self.dir = newDir
            elif self.getX() <= LEFTMARGIN + 10:
                self.setX(LEFTMARGIN + 10)
                newDir = 180 - self.dir
                self.setDirection(newDir)
                self.dir = newDir
            if self.getY() >= BOTTOMMARGIN - 10:
                self.setY(BOTTOMMARGIN - 10)
                newDir = 360 - self.dir
                self.setDirection(newDir)
                self.dir = newDir
            elif self.getY() <= TOPMARGIN + 10:
                self.setY(TOPMARGIN + 10)
                newDir = 360 - self.dir
                self.setDirection(newDir)
                self.dir = newDir
        if isKeyPressed(32):   # SPACE
            self.fire()
        self.firecount -= 1        

class Missile(Actor):
    
    def __init__(self):
        Actor.__init__(self, True, os.path.join(DATAPATH, "missile.png"))
        self.speed = 15
        self.direction = player.dir
    
    def act(self):
        self.move(self.speed)
        if self.getX() >= RIGHTMARGIN:
            win.removeActor(self)
        elif self.getX() <= LEFTMARGIN:
            win.removeActor(self)
        if self.getY() >= BOTTOMMARGIN:
            win.removeActor(self)
        elif self.getY() <= TOPMARGIN:
            win.removeActor(self)

    def collide(self, actor1, actor2):
        xpos = actor2.getX()
        ypos = actor2.getY()
        win.removeActor(self)
        win.removeActor(actor2)
        if actor2.__class__.__name__ == "Tomato":
            t = Tomato()
            win.addActor(t, Location(randint(54, 640 - 14), (randint(54, 640 - 14))))
            t.setDirection(randint(20, 340))
        hit = Explosion()
        win.addActor(hit, Location(xpos, ypos))
#        if player.score == 10:
#            win.doPause()         # Für Screenshot
        player.score += 1
        c = Color.white
        displayScore = TextActor("Score: " + str(player.score), c, BGCOLOR, Font("ComicHelvetic_Medium", Font.BOLD, 20))
        win.addActor(displayScore, Location(40, 20))
        return 0

class Explosion(Actor):
    
    def __init__(self):
        Actor.__init__(self, True, os.path.join(DATAPATH, "explosion.png"))
        self.timer = 5
    
    def act(self):
        self.timer -= 1
        if self.timer <= 0:
            win.removeActor(self)    
 
class Enemy(Actor):
    
    def __init__(self):
        Actor.__init__(self, False, os.path.join(DATAPATH, "alien.png"))
        self.speed = randint(1, 5)
        
    def act(self):
        self.move(self.speed)
        if self.getX() >= RIGHTMARGIN:
            self.setX(RIGHTMARGIN)
            self.direction -= randint(90, 270)
        elif self.getX() <= LEFTMARGIN:
            self.setX(LEFTMARGIN)
            self.direction -= randint(90, 270)
        if self.getY() >= BOTTOMMARGIN:
            self.setY(BOTTOMMARGIN)
            self.direction -= randint(90, 270)
        elif self.getY() <= TOPMARGIN:
            self.setY(TOPMARGIN)
            self.direction -= randint(90, 270)

class Pumpkin(Enemy):
    
    def __init__(self):
        Actor.__init__(self, False, os.path.join(DATAPATH, "pumpkin.png"))
        self.speed = randint(1, 5)

class Tomato(Enemy):
    
    def __init__(self):
        Actor.__init__(self, False, os.path.join(DATAPATH, "tomato.png"))
        self.speed = randint(1, 5)
     
win = makeGameGrid(WIDTH, HEIGHT, 1, BGCOLOR, True)
win.setPosition(1300, 40)
win.setTitle("🍅 Pumpkin Attack, Stage 4 🎃")   # Titelzeile mit Tomaten- und Pumpkin-Emoji (im Editor nicht sichtbar)
win.setSimulationPeriod(20)

bg = Background()
win.addActor(bg, Location(WIDTH//2, HEIGHT//2))

player = Player()
win.addActor(player, Location(320, 320))

for _ in range(NOPUMP):
    p = Pumpkin()
    win.addActor(p, Location(randint(54, 640 - 14), (randint(54, 640 - 14))))
    p.setDirection(randint(20, 340))
for _ in range(NOTOMATOS):
    t = Tomato()
    win.addActor(t, Location(randint(54, 640 - 14), (randint(54, 640 - 14))))
    t.setDirection(randint(20, 340))
    player.addCollisionActor(t)

win.show()
win.doRun()

Der Quellcode ist natürlich auch wieder (mit allen bisher verwendeten Assets) in meinem GitLab-Repositorium zu TigerJython zu finden.

Die bisher veröffentlichten Folgen sind:

Es ist selbst für einen ungeübten Spieler – wie ich es einer bin – nicht allzu schwer, das Spiel zu gewinnen (es ist momentan gewonnen, wenn alle Kürbisse abgeschossen sind und nur noch Killertomaten dem Spieler gefährlich werden können - ein explizites Spielende muß ich dem Spiel aber noch verpassen). Man könnte das Spiel schwieriger gestalten, wenn man zum Beispiel den Killertomaten Intelligenz verpaßt und diese den Spieler jagen läßt. Aber diese (und weitere) Implementierungen überlasse ich Euch. Ich werde dem Spiel noch einen Start- und zwei Ende-Statii (»verloren« und »gewonnen«) mit entsprechenden Bildschirmen spendieren und danach zu den pösen Pizzen zurückkehren. Es kann ja nicht immer Halloween sein.


(Kommentieren) 

image image



Über …

Der Schockwellenreiter ist seit dem 24. April 2000 das Weblog digitale Kritzelheft von Jörg Kantel (Neuköllner, EDV-Leiter Rentner, Autor, Netzaktivist und ehemaliger Hundesportler — Reihenfolge rein zufällig). Hier steht, was mir gefällt. Wem es nicht gefällt, der braucht ja nicht mitzulesen. Wer aber mitliest, ist herzlich willkommen und eingeladen, mitzudiskutieren!

Alle eigenen Inhalte des Schockwellenreiters stehen unter einer Creative-Commons-Lizenz, jedoch können fremde Inhalte (speziell Videos, Photos und sonstige Bilder) unter einer anderen Lizenz stehen.

Der Besuch dieser Webseite wird aktuell von der Piwik Webanalyse erfaßt. Hier können Sie der Erfassung widersprechen.

Diese Seite verwendet keine Cookies. Warum auch? Was allerdings die iframes von Amazon, YouTube und Co. machen, entzieht sich meiner Kenntnis.


Werbung

Diese Spalte wurde absichtlich leergelassen!


Werbung


image  image  image
image  image  image


image