image image


image

Crab: Ein kleines Spiel als Fingerübung in Processing.py

Corona, Klima und jetzt auch noch Afghanistan: Die Welt gerät aus den Fugen und die Politik versagt auf der ganzen Linie! Ich mußte irgend etwas tun, um nicht völlig durchzudrehen. Also habe ich ein kleines Spiel in Processing.py programmiert, denn erstens wird Processing diesen Monat zwanzig und zweitens kann ich mich mit dieser Python-Version von Processing immer noch am besten ablenken. 🤓

Ich muß gestehen, die Idee zu diesem Spiel ist nicht aut meinem Mist gewachsen. Ich habe sie aus dem Buch »Let’s code Python« geklaut, in dem Hauke Fehr (s)eine Version dieses Spiels in TigerJython und GameGrid implementiert hat. Ich habe das Spiel optisch aufgepeppt und an Processing.py angepaßt, aber die Sprites ebenso wie Hauke Fehr aus TigerJythons Sammlung entnommen, um den naiven Charme des Spiels zu erhalten.

Die Spielidee ist recht einfach: Irgendwo auf dem Meeresgrund kriecht eine kleine rote Krabbe, die aus Langeweile herunterschwebende Luftblasen1 mit ihren Scheren einfängt und zum Platzen bringt. Jede geplatzte Luftblase bringt einen Punkt. Da jedes Spiel aber auch einen Gegner braucht, fallen auch schnellere, rote Blasen herunter, die toxisch sind. Kollidiert die Krabbe mit einer dieser roten Blasen, stirbt sie und das Spiel ist zu Ende. Einfacher geht es doch kaum, oder?

Natürlich schreit das – wie fast immer bei einem Spiel – nach einer objektorientierten Implementierung mit den Klassen Crab, Bubble und Enemy. Da – im Gegensatz zu TigerJythons GameGrid – Processing(.py) keine Oberklasse Actor mit vielen nützlichen Default-Verhalten kennt kennt, muß in Processing.py alles selber implementiert werden. Die Klassen werden in setup() initialisiert:

def setup():
    global bg, crab
    size(WIDTH, HEIGHT)
    this.surface.setTitle(u"Jörgs kleines Krabbenspiel")
    font = createFont("Gorditas-Bold.ttf", 40)
    textFont(font)
    bg = loadImage("bg.png")
    crab = Crab()
    for _ in range(N_BUBBLES):
        bubbles.append(Bubble())
    for _ in range(N_ENEMIES):
        enemies.append(Enemy())

Außer der Initialisierung der Klassen werden in setup() eigentlich nur noch das Hinterfrundbild und der verwendete Textfont Gorditas Bold2 geladen.

Die Klasse Crab seht Ihr unten und die Krabbe hat eine Besonderheit: Jedesmal, wenn die Krabbe stopt, wird die Geschwindigkeit auf Null gesetzt und wenn sie wieder anfängt, sich zu bewegen, geht die Beschleunigung vel = 0.5 nur langsam wieder nach oben. Das macht das Spiel ein wenig interessanter, da sich so die Krabbe nur träge in Bewegung setzt und den Gegnern manchmal nicht schnell genug ausweichen kann.

class Crab():
    
    def __init__(self):
        self.pos = PVector(width/2 - 34, height - 80)
        self.img = loadImage("crab.png")
        self.w = 68
        self.speed = PVector(0, 0)
        self.score = 0
        self.vel = 0.5
        self.r = 25
    
    def update(self):
        self.pos.x += self.speed.x
        if self.pos.x >= width - self.w:
            self.pos.x = width - self.w
            self.speed.x = 0
        if self.pos.x <= 0:
            self.pos.x = 0
            self.speed.x = 0

    def display(self):
        image(self.img, self.pos.x, self.pos.y)
    
    def check_collision(self, other):
        distance = dist(self.pos.x, self.pos.y, other.pos.x, other.pos.y)
        if distance < self.r + other.r:
            other.reset()
            self.score += 1

Neben dem Constructor besitzt sie die Methoden update(), display() und check_collision() für die Kollisionserkennung mit den Luftblasen.

Diese Kollisionserkennung ist eine Kreiserkennung, wie ich sie hier schon einmal beschrieben hatte. Sie ist recht grob, aber meiner Meinung nach ausreichend, Luftblasen zerplatzen eben auch schon einmal etwas früher.

class Bubble():
    
    def __init__(self):
        self.pos = PVector(randint(0, width - 30), randint(-640, -30))
        self.img = loadImage("bubble.png")
        self.r = 15
        self.speed = randint(1, 3)
    
    def update(self):
        self.pos.y += self.speed
        if self.pos.y >= height:
            self.reset()
    
    def reset(self):
        self.pos.x = randint(0, width - 30)
        self.pos.y = randint(-640, -30)
        
    def display(self):
        image(self.img, self.pos.x, self.pos.y)

Die Lufblasen Bubble besitzen als zusätzliche Methode ein reset(), die sie, wenn sie zerplatzen oder das Spielefenster unten verlassen, wieder an eine zufällige Position oberhalb des Spielefensters katapultiert.3 Dadurch bleibt die Anzahl der Luftblasen immer gleich.

Die Klasse Enemy ist mit der Klasse Bubble nahezu identisch, außer daß die roten Blasen etwas schneller fallen und sie – analog zu Crab – eine eigene Kollisionserkennungsmethode besitzt.

Nun der komplette Quellcode zum Nachvollziehen und -programmieren. Gesteuert wird die Krabbe über die Pfeiltasten rechts und links: Werden sie gedrückt (Funktion keyPressed()) setzt sich die Krabbe langsam in Bewegung. Auf das explizite Setzen einer Maximalgeschwindigkeit habe ich verzichtet, da die Geschwindigkeit ja sowieso entweder beim Loslassen der Pfeiltasten (Funktion keyReleased()) oder beim Erreichen des rechten oder linken Fensterrandes wieder auf Null gesetzt wird:

from random import randint

WIDTH = 640
HEIGHT = 480
N_BUBBLES = 25
N_ENEMIES = 5

bubbles = []
enemies = []

class Crab():
    
    def __init__(self):
        self.pos = PVector(width/2 - 34, height - 80)
        self.img = loadImage("crab.png")
        self.w = 68
        self.speed = PVector(0, 0)
        self.score = 0
        self.vel = 0.5
        self.r = 25
    
    def update(self):
        self.pos.x += self.speed.x
        if self.pos.x >= width - self.w:
            self.pos.x = width - self.w
            self.speed.x = 0
        if self.pos.x <= 0:
            self.pos.x = 0
            self.speed.x = 0

    def display(self):
        image(self.img, self.pos.x, self.pos.y)
    
    def check_collision(self, other):
        distance = dist(self.pos.x, self.pos.y, other.pos.x, other.pos.y)
        if distance < self.r + other.r:
            other.reset()
            self.score += 1

class Bubble():
    
    def __init__(self):
        self.pos = PVector(randint(0, width - 30), randint(-640, -30))
        self.img = loadImage("bubble.png")
        self.r = 15
        self.speed = randint(1, 3)
    
    def update(self):
        self.pos.y += self.speed
        if self.pos.y >= height:
            self.reset()
    
    def reset(self):
        self.pos.x = randint(0, width - 30)
        self.pos.y = randint(-640, -30)
        
    def display(self):
        image(self.img, self.pos.x, self.pos.y)

class Enemy():
    
    def __init__(self):
        self.pos = PVector(randint(0, width - 30), randint(-640, -30))
        self.img = loadImage("enemy.png")
        self.r = 15
        self.speed = randint(2, 4)
    
    def update(self):
        self.pos.y += self.speed
        if self.pos.y >= height:
            self.reset()
    
    def reset(self):
        self.pos.x = randint(0, width - 30)
        self.pos.y = randint(-640, -30)
            
    def display(self):
        image(self.img, self.pos.x, self.pos.y)
        
    def check_collision(self, other):
        distance = dist(self.pos.x, self.pos.y, other.pos.x, other.pos.y)
        if distance < self.r + other.r:
            return(True)

def setup():
    global bg, crab
    size(WIDTH, HEIGHT)
    this.surface.setTitle(u"Jörgs kleines Krabbenspiel")
    font = createFont("Gorditas-Bold.ttf", 40)
    textFont(font)
    bg = loadImage("bg.png")
    crab = Crab()
    for _ in range(N_BUBBLES):
        bubbles.append(Bubble())
    for _ in range(N_ENEMIES):
        enemies.append(Enemy())
    
def draw():
    background(49, 197, 244)
    image(bg, 0, 0)
    for bubble in bubbles:
        bubble.update()
        crab.check_collision(bubble)
        bubble.display()
    for enemy in enemies:
        enemy.update()
        if enemy.check_collision(crab):
            textAlign(CENTER)
            fill(250, 0, 0)
            textSize(40)
            text("Game Over", width/2, height/2)
            noLoop()
        enemy.display()
    crab.update()
    crab.display()
    fill(250, 0, 0)
    textAlign(LEFT)
    textSize(30)
    text("Score: " + str(crab.score), 15, 40)
        
def keyPressed():
    # global crab
    if key == CODED:
        if keyCode == LEFT:
            crab.speed.x -= crab.vel
        elif keyCode == RIGHT:
            crab.speed.x += crab.vel

def keyReleased():
    if key == CODED:
        if keyCode == LEFT or keyCode == RIGHT:
            crab.speed.x = 0

Das Spiel ist gerade noch so einfach, daß selbst ich als ungeübter Gelegenheitsspieler (mir macht das Programmieren von Spielen viel mehr Spaß als das Spielen selber) ungeahnte Höhenflüge mit einem Score von über 100 Punkten damit erreichen konnte:

image

Bis ich diesen »High Score« aber erreichen hatte, gab es aber etliche Versuche davor, in denen ich die Unbillen der Welt vergessen konnte. So hat das Spiel doch seinen Zweck erreicht und ich erkläre es zu meinem Beitrag zum Processing Community Day. 🍰🎂🍰🎂🍰🎂

Den Quellcode und die Assets gibt es wie immer auch in meinem GitHub-Repositorium.

  1. Ich weiß, in der realen Welt schweben Luftblasen nach oben, aber hey, es ist mein Spiel! 

  2. Gorditas Bold ist ein Font von Gustave Dipre aus Googles freier Font-Sammlung (Open Font Licence). 

  3. Die gleiche reset()-Methode ist noch einmal in Enemy implementiert. Ich weiß, das hätte man – um Code-Verdoppelungen zu vermeiden – auch anders implementieren können (zum Beispiel eine Oberklasse NonPlayer mit abgeleiteten Klassen Bubble und Enemy), aber ich wollte die Implementierung möglichst einfach und verständlich halten. 


(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 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