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!
Ü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