image image


image

Der Rote Baron und die Aliens (Stage 2)

Meine Erkundungen von Pygame Zero und dem Roten Baron gehen in die nächste Runde. Zum einen habe ich dem Helden ein Geschoß spendiert, damit er sich verteidigen kann. Doch zur Verteidigung braucht man Gegner, gegen die es sich zu verteidigen gilt. In völliger Mißachtung jeglicher historischer (und politischen) Korrektheit muß sich unser Jagdflieger aus dem 1. Weltkrieg gegen UFO-fliegende Aliens wehren. Diese habe ich ehrlicherweise nur aus dem Grund ausgewählt, weil die (freien) Sprites von Kenney so schön bunt waren.

Und ich mußte eine weitere Entscheidung treffen: Um den Code besser gliedern zu können und die Übersicht zu behalten, habe ich dem Spieler, den Aliens und dem Geschoß jeweils eine eigene, von Actor() abgeleitete Klasse spendiert, da ich bei ihnen jeweils die bei Actor() leere Methode update() überschreiben mußte, weil ich ich diese Methode im Hauptprogramm klein und übersichtlich halten wollte. Für den Roten Baron (Klasse Plane()) sieht die Methode wie folgt aus:

    def update(self):
        self.velocity += self.gravity
        self.velocity *= 0.9         # Reibung/Luftwiderstand
        self.y += self.velocity
        if self.y >= groundlevel:    # Flugzeug ist am Boden
            self.y = groundlevel
            self.velocity = 0
        if self.y <= 20:             # Flugzeug ist am oberen Fensterrand
            self.y = 20
            self.velocity = 0
        self.show()
    
    def up(self):
        self.velocity += self.upforce
    
    def show(self):
        self.count += 1
        if self.count > 9: self.count = 0
        if self.count < 3:
            self.image = "planered1"
        elif self.count < 6:
            self.image = "planered2"
        else:
            self.image = "planered3"

Dabei ist die Methode up() ein Callback, der beim Drücken der Pfeiltaste nach oben ausgelöst wird. Einen ähnlichen Callback besitzt auch die Klasse Bullet() für das Geschoß, nur das diese eine Art An-/Ausschalter auslöst: Ist sie ausgelöst, bewegt sich das Geschoß nach vorne, während es sich im Ruhezustand im Doppeldecker versteckt.

    def update(self):
        if not self.fire:
            self.x = plane.x
            self.y = plane.y
        else:
            self.x += self.speed
            if self.left >= WIDTH:
                self.fire = False

Das hat zur Folge, daß die Aliens, deren Update wegen der Kollisionserkennung zu einem großen Teil doch in der update()-Methode des Hauptprogramms stattfindet, in der Kollisionserkennung nicht nur prüft, ob eine Kollision mit der Kugel stattindet, sondern ob die Kugel während der Kollision auch abgefeuert wurde (bullet.fire == True). Denn andernfalls könnte der Rote Baron ungestraft ein unfaires Kamikaze-Verhalten an den Tag legen, denn das im Flieger versteckte Geschoß würde die Aliens ebenfalls erwischen, ohne daß dies Konsequenzen für den Spieler hätte:

    for enemy in enemies:
        if enemy.colliderect(bullet) and bullet.fire:
            enemy.reset()
            bullet.fire = False
        enemy.update()

Die update()-Methode von Enemy() selber ruft, wenn das UFO über den linken Bildschirmrand hinausgeglitten ist, das gleiche reset() wie oben auf. Durch diese Methode wird das UFO rechts außen mit neuer Besatzung und neuen Farben neu positioniert:

    def reset(self):
        self.x = randint(WIDTH + 50, WIDTH + 500)
        self.y = randint(25, groundlevel)    
        self.image = choice(enemyships)

Damit steht das Grundgerüst meiner wilden Mixture aus Flappy Bird und Sidescrolling Shooter: Der Rote Baron kann die Aliens abschießen und ihnen aus dem Weg gehen. Natürlich fehlt noch das Salz in der Suppe: Das Spiel sollte zu Ende sein oder zumindest sollte der Rote Baron ein Leben verlieren (das heißt, sein Doppeldecker wird beschädigt), wenn er mit einem UFO kollidiert. Und jeder Abschuß eines UFOs sollte mit Punkten belohnt werden, wärend es Strafpunkte hagelt, wenn ein Alien entkommt. Und sinkt das Punktekonto unter Null, sollte das Spiel ebenfalls zu Ende sein.

Außerdem bedürfen die Konstanten für die Gravitation, die Reibung und die Kraft, die den Flieger nach oben schnellen läßt, ebenfalls noch des Feintunings. Und ein wenig optisches Aufmotzen könnte auch nicht schaden. Ihr seht also, es gibt noch einiges zu tun.

Bis dahin aber erst einmal den vollständigen Quelltext dieser Fassung, damit Ihr ebenfalls experimentieren und Euer eigenes Feintuning vornehmen könnt. Und wie immer gibt es den Quelltext und die Assets auch in meinem GitHub-Repositorium.

import pgzrun
import sys
from random import randint, choice

WIDTH = 800
HEIGHT = 480
TITLE = "The Red Baron 2"
left = WIDTH/2
bottom = HEIGHT/2
bottomground = HEIGHT - 35

BACKGROUND = "background"
GROUND     = "groundgrass"
no_enemies = 10
enemyships = ["shipbeige", "shipblue", "shipgreen", "shippink", "shipyellow"]

back0 = Actor(BACKGROUND, (left, bottom))
back1 = Actor(BACKGROUND, (WIDTH + left, bottom))
backs = [back0, back1]
ground0 = Actor(GROUND, (left, bottomground))
ground1 = Actor(GROUND, (WIDTH + left, bottomground))
grounds = [ground0, ground1]
groundlevel = HEIGHT - 85

class Plane(Actor):
    
    def __init__(self, image, x, y):
        Actor.__init__(self, image, (x, y))
        self.count = 0
        self.gravity = 0.1
        self.upforce = -15
        self.velocity = 0
        
    def update(self):
        self.velocity += self.gravity
        self.velocity *= 0.9         # Reibung/Luftwiderstand
        self.y += self.velocity
        if self.y >= groundlevel:    # Flugzeug ist am Boden
            self.y = groundlevel
            self.velocity = 0
        if self.y <= 20:             # Flugzeug ist am oberen Fensterrand
            self.y = 20
            self.velocity = 0
        self.show()
    
    def up(self):
        self.velocity += self.upforce
    
    def show(self):
        self.count += 1
        if self.count > 9: self.count = 0
        if self.count < 3:
            self.image = "planered1"
        elif self.count < 6:
            self.image = "planered2"
        else:
            self.image = "planered3"

class Bullet(Actor):
    
    def __init__(self):
        Actor.__init__(self, "laserred")
        self.x = plane.x
        self.y = plane.y
        self.speed = 25
        self.fire = False
    
    def update(self):
        if not self.fire:
            self.x = plane.x
            self.y = plane.y
        else:
            self.x += self.speed
            if self.left >= WIDTH:
                self.fire = False

class Enemy(Actor):
    
    def __init__(self, image, x, y):
        Actor.__init__(self, image, (x, y))
        self.width = 44
        self.speed = -1.5

    def reset(self):
        self.x = randint(WIDTH + 50, WIDTH + 500)
        self.y = randint(25, groundlevel)    
        self.image = choice(enemyships)

    def update(self):
        self.x += self.speed
        if self.x <= -self.width:
            self.reset()

plane = Plane("planered1", 100, HEIGHT//2)
bullet = Bullet()
enemies = []
for _ in range(no_enemies):
    enemyship = choice(enemyships)
    enemy = Enemy(enemyship, randint(WIDTH + 50, WIDTH + 750), randint(50, groundlevel))
    enemies.append(enemy)

def update():
    for back in backs:
        back.x -= 0.4
        if back.x <= -left:
            back.x = WIDTH + left
    for ground in grounds:
        ground.x -= 0.6
        if ground.x <= -left:
            ground.x = WIDTH + left
    bullet.update()
    for enemy in enemies:
        if enemy.colliderect(bullet) and bullet.fire:
            enemy.reset()
            bullet.fire = False
        enemy.update()
    plane.update()

def draw():
    for back in backs:
        back.draw()
    for ground in grounds:
        ground.draw()
    for enemy in enemies:
        enemy.draw()
    bullet.draw()
    plane.draw()


def on_key_down():
    ## Spielende mit ESC
    if keyboard.escape:
        sys.exit()
    ## Hoch mit Pfeiltaste nach oben
    if keyboard.up:
        plane.up()
    ## Feuern mit rechter Pfeiltaste
    if keyboard.right:
        bullet.fire = True
        
pgzrun.go()

Bei der Entwicklung der Mechanik des Doppeldeckers haben mir folgende P5.js-Video-Tutorials von Daniel Shiffman geholfen:

  1. Die Coding Challenge 31 »Flappy Bird« und die Coding Challenge 41 »Clappy Bird«, ein Flappy-Bird-Klon mit Audio-Steuerung.
  2. Und dann ist da noch die fünfteilige Coding Challenge 100 Neuroevolution Flappy Bird, Part 1, Part 2, Part 3, Part 4 und Part 5, denn ich habe nicht vergessen, daß mein geplantes Projekt »Retrogaming und Künstliche Intelligenz« heißt. Ich weiß ja nicht, ob Flappy Bird schon »retro« ist, aber ich erkläre es jetzt einfach mal dazu.
  3. Und dann ist da auch noch Shiffmans Video »Neuroevolution Flappy Bird with TensorFlow.js«. Das sollte mir Ansporn sein, Pygame Zero mit TensorFlow für Python (und mit Keras) zu verheiraten.

War sonst noch was? Ach ja, nicht nur Daniel Shiffman, sondern auch der YouTube-User Ihabexe hat eine Flappy-Bird-Version in P5.js programmiert und das Video dazu hochgeladen. Kann man sich zur Inspiration ja auch noch reinziehen.


(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