In diesem dritten Teil meiner kleinen Tutorial-Reihe zur Spieleprogrammierung mit Python und PyGlet (hier Teil 1 und Teil 2) möchte ich dem kleinen Raumschiff die entsprechenden Gegner spendieren. Dazu habe ich aus dem freien Set (CC-BY-3.0) von Jacob Zinman-Jeanes fünf Spritesheets mit farbigen Ufos entnommen, die zitternd über den Bildschirm schweben und sich bedrohlich auf das Raumschiff zubewegen sollen. Im Endeffekt wird es so aussehen, wie dieser Screenshot (und glaubt mir oder programmiert es nach: Sowohl die UFOs wie auch die Rakete sind animiert):
Als erstes stand mal wieder ein Refactoring an: Ich hielt es für angebracht, alle Spielobjekte, das heißt sowohl den Sternenhintergrund, die UFOs wie auch den Spieler als Unterklassen der Klasse GameObject
zu implementieren. Die entsprechende Datei habe ich gameobjects2.py
genannt und die Unterklassen hinzugefügt:
class Player(GameObject):
def __init__(self, posx, posy, sprite = None):
super().__init__(posx, posy, sprite)
def update(self, dt):
self.posx += self.velx*dt
self.posy += self.vely*dt
if self.posy >= HEIGHT - 40:
self.posy = HEIGHT - 40
if self.posy <= 10:
self.posy = 10
self.sprite.x = self.posx
self.sprite.y = self.posy
class Ufo(GameObject):
def __init__(self, posx, posy, sprite = None):
super().__init__(posx, posy, sprite)
self.speed = -100
class Space(GameObject):
def __init__(self, posx, posy, sprite = None):
super().__init__(posx, posy, sprite)
Die (neue Klasse) Ufo
wie auch die Klasse Space
unterscheiden sich (bis auf die UFO-Geschwindigkeit) bisher noch nicht groß von der Klasse GameObject
– das wird sich aber in einer der nächsten Folgen zumindest für die UFOs noch ändern.
Neu an der Klasse Player
ist, daß dieser nun ein Randabfrage bekommen hat. Die Rakete kann nun das Spielfenster nicht mehr verlassen. Die Randabfrage für die UFOs habe ich – da diese in einer Liste erolgt – im Hauptprogramm belassen. In diesem wurden als erstes die entsprechenden import
-Statements angepaßt:
import pyglet
from pyglet.window import FPSDisplay, key
from pyglet.sprite import Sprite
from gameobjects2 import GameObject, Player, Ufo, Space, preload_image, preload_image_animation
import os
Ähnlich dem Hintergrund werden nun die Bilder der UFOs in einer Liste vorgeladen und die Objekte selber dann ein einer Schleife initialisiert:
# Gegner
self.ufo_list = []
self.ufo_img1 = preload_image_animation("eSpritesheet_40x30.png", 6, 1, 40, 30)
self.ufo_img2 = preload_image_animation("eSpritesheet_40x30_hue1.png", 6, 1, 40, 30)
self.ufo_img3 = preload_image_animation("eSpritesheet_40x30_hue4.png", 6, 1, 40, 30)
self.ufo_img4 = preload_image_animation("eSpritesheet_40x30_hue2.png", 6, 1, 40, 30)
self.ufo_img5 = preload_image_animation("eSpritesheet_40x30_hue3.png", 6, 1, 40, 30)
for i in range(10):
self.ufo_list.append(Ufo(840, 540 - i*40, Sprite(self.ufo_img1)))
self.ufo_list.append(Ufo(760, 540 - i*40, Sprite(self.ufo_img2)))
self.ufo_list.append(Ufo(680, 540 - i*40, Sprite(self.ufo_img3)))
self.ufo_list.append(Ufo(600, 540 - i*40, Sprite(self.ufo_img4)))
self.ufo_list.append(Ufo(520, 540 - i*40, Sprite(self.ufo_img5)))
Das Vorladen der UFO-Bilder hätte bei entsprechender Umbenennung der Dateinamen natürlich auch in einer Schleife geschehen können, aber ich hatte ursprünglich nur zwei Spritesheets vorgesehen. Doch als ich dann sah, wie smooth die Animationen abliefen, habe ich die anderen drei Spritesheets einfach per copy and paste dazugeladen.
Die on_draw()
-Funktion mußte natürlich um die Zeilen ergänzt werden, die die UFOs zeichnen sollen. Daher sind diese beiden Zeilen hinzugekommen:
for ufo in self.ufo_list:
ufo.draw()
Für das Update der UFOs habe ich eine eigene Funktion geschrieben:
def update_ufo(self, dt):
for ufo in self.ufo_list:
ufo.update(dt)
if ufo.posy <= 10:
ufo.posy = 10
ufo.posx -= 40
ufo.speed *= -1
if ufo.posy >= HEIGHT - 50:
ufo.posy = HEIGHT - 50
ufo.posx -= 40
ufo.speed *= -1
ufo.vely = ufo.speed
Die Ränderbehandlung scheint ein wenig seltsam. Ziel war es jedoch – analog zu dem originalen Space Invaders – die UFOs um eine Reihe nach vorne rücken zu lassen, sobald sie den Fensterrand erreicht haben. Darum auch der Abstand von 80 Pixeln je UFO-Reihe. Da die UFOs jeweils 30 Pixel breit sind, überlappen sie sich dann nicht beim Vorrücken auf den Spieler.
Nach all diesen Vorbereitungen mußte die update()
-Funktion nur ein zusätzliche Zeile spendiert bekommen, damit alles wie gewünscht läuft. Sie sieht nun so aus:
def update(self, dt):
self.player.update(dt)
self.update_ufo(dt)
self.update_space(dt)
Wie gewohnt nun das vollständige Programmlisting, damit Ihr das bisherige Vorgehen auch nachempfinden und -programmieren könnt. Zuerst die Datei gameobjects2.py
:
import pyglet
from pyglet.sprite import Sprite
WIDTH = 900
HEIGHT = 600
def preload_image(image):
img = pyglet.image.load("images/" + image)
return img
def preload_image_animation(image, image_row, image_col, image_width, image_height):
img = pyglet.image.load("images/" + image)
img_seq = pyglet.image.ImageGrid(img, image_row, image_col, item_width = image_width, item_height = image_height)
img_texture = pyglet.image.TextureGrid(img_seq)
img_anim = pyglet.image.Animation.from_image_sequence(img_texture[:], 0.1, loop = True)
return img_anim
class GameObject():
def __init__(self, posx, posy, sprite = None):
self.posx = posx
self.posy = posy
self.velx = 0
self.vely = 0
if sprite is not None:
self.sprite = sprite
self.sprite.x = self.posx
self.sprite.y = self.posy
def draw(self):
self.sprite.draw()
def update(self, dt):
self.posx += self.velx*dt
self.posy += self.vely*dt
self.sprite.x = self.posx
self.sprite.y = self.posy
class Player(GameObject):
def __init__(self, posx, posy, sprite = None):
super().__init__(posx, posy, sprite)
def update(self, dt):
self.posx += self.velx*dt
self.posy += self.vely*dt
if self.posy >= HEIGHT - 40:
self.posy = HEIGHT - 40
if self.posy <= 10:
self.posy = 10
self.sprite.x = self.posx
self.sprite.y = self.posy
class Ufo(GameObject):
def __init__(self, posx, posy, sprite = None):
super().__init__(posx, posy, sprite)
self.speed = -100
class Space(GameObject):
def __init__(self, posx, posy, sprite = None):
super().__init__(posx, posy, sprite)
Und dann das Hauptprogramm, das sich in der Datei stage03.py
befindet:
import pyglet
from pyglet.window import FPSDisplay, key
from pyglet.sprite import Sprite
from gameobjects2 import GameObject, Player, Ufo, Space, preload_image, preload_image_animation
import os
TITLE = "Space Invaders Stage 3"
PLAYERSPEED = 300
SPACESPEED = -50
WIDTH = 900
HEIGHT = 600
class GameWindow(pyglet.window.Window):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_location(100, 100)
self.frame_rate = 1/60.0
self.fps_display = FPSDisplay(self)
self.fps_display.label.font_size = 24
# Hier wird der Pfad zum Verzeichnis des ».py«-Files gesetzt
# Erspart einem das Herumgehample in TextMate mit dem os.getcwd()
# und os.path.join()
file_path = os.path.dirname(os.path.abspath(__file__))
os.chdir(file_path)
# Hintergrund
self.space_list = []
self.space_img = preload_image("farback.gif")
for i in range(2):
self.space_list.append(Space(i*1782, 0, Sprite(self.space_img)))
for space in self.space_list:
space.velx = SPACESPEED
# Gegner
self.ufo_list = []
self.ufo_img1 = preload_image_animation("eSpritesheet_40x30.png", 6, 1, 40, 30)
self.ufo_img2 = preload_image_animation("eSpritesheet_40x30_hue1.png", 6, 1, 40, 30)
self.ufo_img3 = preload_image_animation("eSpritesheet_40x30_hue4.png", 6, 1, 40, 30)
self.ufo_img4 = preload_image_animation("eSpritesheet_40x30_hue2.png", 6, 1, 40, 30)
self.ufo_img5 = preload_image_animation("eSpritesheet_40x30_hue3.png", 6, 1, 40, 30)
for i in range(10):
self.ufo_list.append(Ufo(840, 540 - i*40, Sprite(self.ufo_img1)))
self.ufo_list.append(Ufo(760, 540 - i*40, Sprite(self.ufo_img2)))
self.ufo_list.append(Ufo(680, 540 - i*40, Sprite(self.ufo_img3)))
self.ufo_list.append(Ufo(600, 540 - i*40, Sprite(self.ufo_img4)))
self.ufo_list.append(Ufo(520, 540 - i*40, Sprite(self.ufo_img5)))
player_spr = Sprite(preload_image_animation("Spritesheet_64x29.png", 4, 1, 64, 29))
self.player = Player(50, 300, player_spr)
def on_key_press(self, symbol, modifiers):
if symbol == key.UP:
self.player.vely = PLAYERSPEED
if symbol == key.DOWN:
self.player.vely = -PLAYERSPEED
if symbol == key.ESCAPE:
pyglet.app.exit()
def on_key_release(self, symbol, modifiers):
if symbol in (key.UP, key.DOWN):
self.player.vely = 0
def on_draw(self):
self.clear()
for space in self.space_list:
space.draw()
self.player.draw()
for ufo in self.ufo_list:
ufo.draw()
self.fps_display.draw()
def update_space(self, dt):
for space in self.space_list:
space.update(dt)
if space.posx <= -1882:
self.space_list.remove(space)
self.space_list.append(GameObject(1682, 0, Sprite(self.space_img)))
space.velx = SPACESPEED
def update_ufo(self, dt):
for ufo in self.ufo_list:
ufo.update(dt)
if ufo.posy <= 10:
ufo.posy = 10
ufo.posx -= 40
ufo.speed *= -1
if ufo.posy >= HEIGHT - 50:
ufo.posy = HEIGHT - 50
ufo.posx -= 40
ufo.speed *= -1
ufo.vely = ufo.speed
def update(self, dt):
self.player.update(dt)
self.update_ufo(dt)
self.update_space(dt)
win = GameWindow(900, 600, TITLE, resizable = False)
pyglet.clock.schedule_interval(win.update, win.frame_rate)
pyglet.app.run()
Ich habe dieser Tutorial-Reihe ein GitHub-Repositorium spendiert, in dem Ihr nicht nur den Quellcode, sondern auch alle bisher verwendeten Assets finden könnt. Doch denkt immer daran: Man lernt mehr, wenn man den Quellcode nicht einfach kopiert, sondern ihn Zeile für Zeile abschreibt.
Für die nächste Folge plane ich, für den Spieler eine Laserrakete zu implementieren, damit er sich gegen die herannahenden UFOs auch verteidigen kann. Dafür muß dann natürlich auch eine Kollisionserkennung implementiert werden. 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