image image


image

Apple Invaders mit Pygame (Stage 1)

Natürlich möchte ich den Bewegungsalgorithmus, den ich gestern vorgestellt hatte, auch praktisch in Pygame anwenden. Als ein erstes Beispiel habe ich mir das auf einen Schulwettbewerb zurückgehende und von mir schon einmal in Processing.py realisierte Apple Invaders ausgesucht, das eventuell auch noch ein paar weitere Algorithmen, die ich in der nächsten Zeit vorstellen möchte, berücksichtigen wird.

Damit ist klar, es wird kein Nachbau, sondern eine Erweiterung des bisherigen Apple Invaders. Und wer weiß, vielleicht komme ich auf den Geschmack und realisiere diese neue Fassung dann auch noch einmal (vielleicht sogar mit Maussteuerung) in Processing.py. Die Bilder dafür habe ich ja schon.

Aber zuerst einmal geht es völlig ohne Bilder – nur mit Quadraten und Rechtecken. Und natürlich setzt das Programm auf mein Pygame Template auf. Aber zuerst einmal habe ich eine Datei sprites.py angelegt und dort eine Klasse Player() und eine Klasse Block() untergebracht:

# Sprite Klassen
import pygame as pg
from settings import Settings
vec = pg.math.Vector2

s = Settings()

class Player(pg.sprite.Sprite):
    
    def __init__(self, world):
        pg.sprite.Sprite.__init__(self)
        self.world = world
        self.image = pg.Surface((32, 32))
        self.image.fill(s.YELLOW)
        self.rect = self.image.get_rect()
        self.rect.center = (s.WIDTH/2, s.HEIGHT/2)
        self.pos =vec(s.WIDTH/2, s.HEIGHT/2)
        self.vel = vec(0, 0)
        self.acc = vec(0, 0)
    
    def jump(self):
        # Nur springen, wenn man auf einem Block steht
        self.rect.x += 1
        colls = pg.sprite.spritecollide(self, self.world.blocks, False)
        self.rect.x -= 1
        if colls:
            self.vel.y = s.PLAYER_JUMP
    
    def update(self):
        self.acc = vec(0, s.PLAYER_GRAV)
        keys = pg.key.get_pressed()
        if keys[pg.K_LEFT]:
            self.acc.x = -s.PLAYER_ACC
        if keys[pg.K_RIGHT]:
            self.acc.x = s.PLAYER_ACC
        
        # Bewegungsgleichungen
        self.acc.x += self.vel.x*s.PLAYER_FRICTION
        self.vel += self.acc
        self.pos += self.vel
        
        # Randbehandlung
        if self.pos.x >= s.WIDTH - s.TILESIZE/2:
            self.pos.x = s.WIDTH - s.TILESIZE/2
        if self.pos.x <= s.TILESIZE/2:
            self.pos.x = s.TILESIZE/2
        
        self.rect.midbottom = self.pos

class Block(pg.sprite.Sprite):
    def __init__(self, x, y, w, h):
        pg.sprite.Sprite.__init__(self)
        self.image = pg.Surface((w, h))
        self.image.fill(s.GREEN)
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y

Da diese Klassen Konstanten aus Settings() benötigen, muß diese importiert und initialisiert werden. Wie im gestrigen Beitrag ist der Spieler zur Zeit noch einfach ein gelbes Quadrat auf schwarzem Grund und auch die update() Methode entspricht dem gestrigen Beispiel. Neu ist eigentlich nur die Methode jump(), denn in meiner neuen Version muß der Held auch springen können, da es zwei Plattform-Ebenen gegen die bösen Äpfel zu verteidigen gilt. In der Methonde jump() soll sichergestellt werden, daß der Spieler nur springen kann, wenn er festen Boden unter den Füßen hat – sonst wäre es ja nicht springen, sondern fliegen. Dazu wird ein einfacher Trick verwendet: Die Position des Players wird um einen Pixel nach unten verschoben und dann wird überprüft, ob er mit einem Block kollidiert. Danach wird er wieder auf seine alte Position gesetzt. Da zu diesem Zeitpunkt ja noch nichts auf dem Monitor gezeichnet wird, ist dieser Trick völlig unauffällig.

    def jump(self):
        # Nur springen, wenn man auf einem Block steht
        self.rect.x += 1
        colls = pg.sprite.spritecollide(self, self.world.blocks, False)
        self.rect.x -= 1
        if colls:
            self.vel.y = s.PLAYER_JUMP

Pygames Kollisionsbehandlung sammelt alle Kollisionen in einer Liste. Nur wenn diese nicht leer ist, steht der Spieler auf einer Plattform und kann springen, indem seine Velocity in y-Richtung auf -12 gesetzt wird. Dieser Wert ist völlig artifiziell und ich habe ihn durch wildes Experimentieren herausgefunden. Ihr seid natürlich aufgefordert, mit eigenen Werten zu spielen, daher habe ich ihn als PLAYER_JUMP = -12 auch in die Konfigurationsdatei verbannt.

Zur Klasse Block() ist momentan noch nicht viel zu sagen. Ein Block besteht einfach aus einem grünen Rechteck, auf dem der Spieler stehen und von dem er hochhüpfen kann. Einzige Beachtung sollte die Tatsache finden, daß dieses Spiel kein Super-Mario-Klon ist: Springt der Spieler einen Block von unten an, kann er quasi durch ihn hindurchspringen und auf der Oberfläche des Blockes landen. Diese Technik ist aber bei vielen Plattformspielen verbreitet.

Nun möchte ich Euch meine Settings() vorstellen:

# Konstanten und Einstellungen für das Spiel
import pygame as pg

class Settings():

    def __init__(self):

        # Einige nützliche Konstanten
        self.TITLE = "🍎 Apple Invaders 🍏 Stage 1: Jumping"
        self.WIDTH = 640
        self.HEIGHT = 480
        self.FPS = 60   # Framerate
        self.TILESIZE = 32

        # Player Eigenschaften
        self.PLAYER_ACC = 0.5
        self.PLAYER_FRICTION = -0.12
        self.PLAYER_GRAV = 0.4
        self.PLAYER_JUMP = -12

        # Block Eigenschaften
        self.BLOCKS_LIST = [(0, self.HEIGHT - 64, self.WIDTH, 32),
               (self.WIDTH/2 - 64, self.HEIGHT - 192, 128, 32),
               (0, self.HEIGHT - 192, 128, 32),
               (self.WIDTH - 128, self.HEIGHT - 192, 128, 32)
               ]

        # Nützliche Farbdefinitionen 
        self.WHITE = (255, 255, 255)
        self.BLACK = (0, 0, 0)
        self.RED = (255, 0, 0)
        self.GREEN = (0, 255, 0)
        self.BLUE = (0, 0, 255)
        self.YELLOW = (255, 255, 0)
        self.GREY = (160, 160, 160)

Es ist wie in den vorherigen Beispielen auch einfach eine Sammlung von Konstanten. Neu ist nur, daß die Positionen der Blöcke in einer Liste gespeichert wurden. Das erleichtert die Programmierung im Hauptprogramm, das ich nun vorstellen werde:

# Apple Invaders Stage 2: Jumping

import pygame as pg
from settings import Settings
from sprites import Player, Block
import random
import os

class World:
    
    def __init__ (self):
        # Initialisiert die Spielewelt
        pg.init()
        pg.mixer.init()
        self.screen = pg.display.set_mode((s.WIDTH, s.HEIGHT))
        pg.display.set_caption(s.TITLE)
        self.clock = pg.time.Clock()
        self.keep_going = True
    
    def new(self):
        # Initialisieren und/oder Zurücksetzen
        self.all_sprites = pg.sprite.Group()
        self.blocks = pg.sprite.Group()
        self.player = Player(w)
        self.all_sprites.add(self.player)
        for block in s.BLOCKS_LIST:
            b = Block(*block)
            self.all_sprites.add(b)
            self.blocks.add(b)
        self.run()
        
    def run(self):
        # Game Loop
        self.playing = True
        while self.playing:
            self.clock.tick(s.FPS)
            self.events()
            self.update()
            self.draw()
    
    def update(self):
        # Game-Loop Update
        self.all_sprites.update()
        # Nur Kollisionserkennung, wenn der Spieler fällt
        if self.player.vel.y > 0:
            colls = pg.sprite.spritecollide(self.player, self.blocks, False)
            if colls:
                self.player.pos.y = colls[0].rect.top
                self.player.vel.y = 0
    
    def events(self):
        # Game-Loop Events
        for event in pg.event.get():
            if event.type == pg.QUIT or (event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE):
                if self.playing:
                    self.playing = False
                self.keep_going = False
            if event.type == pg.KEYDOWN:
                if (event.key == pg.K_a) or (event.key == pg.K_SPACE):
                    self.player.jump()
  
    def draw(self):
        # Game-Loop Draw
        self.screen.fill(s.BLACK)
        self.all_sprites.draw(self.screen)
        pg.display.flip()

    def splash_screen(self):
        # Start-Screen
        pass
        
    def game_over(self):
        pass
        
s = Settings()
w = World()
w.splash_screen()
while w.keep_going:
    w.new()
    w.game_over()
    
pg.quit()

Auch hier wird Euch vieles aus den beiden vorherigen Programmen bekannt vorkommen. Es gibt eigentlich nur Änderungen in den new()-, update()- und events()-Methoden. In new() bekommen die Blöcke eine eigene Spritegroup (das vereinfacht später die Kollisionserkennung) und das Hinzufügen der einzelnen Blöcke in beide Spritegroups wird in einer Schleife über BLOCK_LIST erledigt.

Natürlich kann der Spieler jetzt fallen, wenn er nicht auf einem Block einer Plattform steht. Er fällt dann mit der Beschleunigung einer Grafitationsdkonstante PLAYER_GRAV = 0.4 aus Settings(). Die Kollisionserkennung des Spielers mit einem Block wird nur abgefragt, wenn der Spieler tatsächlich fällt (if self.player.vel.y > 0:) und wenn (mindestens) eine Kollision erkannt wird, wird die Velocity wieder auf Null gesetzt.

Ja, und in events() erfolgt nun auch die Tastaturabfrage, ob der Player springen soll. Bei fast allen Jump’n’Run-Spielen erfolgt dies mit der Leertaste (K_SPACE). Da diese auf meinem betagten Notebook schon etwas wackelig ist, und ich sie daher bei meinen Tests nicht überanspruchen will, lasse ich auch noch die Taste »a« (K_a) zu.

Das ist es: Wenn Ihr das Spiel (fehlerfrei) abgetippt habt und es laufen laßt, fällt ein kleines, gelbes Quadrat von der Mitte des Bildschirms auf die oberste Plattform. Von dort könnt Ihr es mit den Pfeiltasten nach links und rechts schicken und mit der Leertaste (oder der Taste a) hüpfen lassen. Das war doch schon ganz nett für den gestrigen, verregneten Sonntag, oder?

In einem nächsten Beitrag möchte ich den Spieler und die Plattformen durch nettere Bildchen ersetzten und – im Falle der Figur des Spielers – diese auch animieren. Still digging!

image


(Kommentieren) 

image image



Über …

Der Schockwellenreiter ist seit dem 24. April 2000 das Weblog digitale Kritzelheft von Jörg Kantel (Neuköllner, EDV-Leiter, 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


Werbung


image  image  image
image  image  image


image