Python unter Linux: PyGame
PyGame ist eine Gruppe von Modulen, die einen Programmierer bei der Erstellung von Spielen unterstützt. Diese Module beinhalten Zugriff auf genau ein Grafikfenster. PyGame unterstützt den Entwickler mit Grafikprimitiven, einfachem Soundzugriff sowie Sprites und vielem mehr. Typische GUI-Elemente gibt es in diesen Modulen jedoch nicht. PyGame beinhaltet eine Grafikbibliothek, zu der Wikibooks auch ein Buch hat, nämlich SDL. Einige der hier angeführten Beispiele sind diesem Buch entlehnt. Hintergrundinformationen zu den Modulen findet sich auf der PyGame-Webseite.
Ein Grafikfenster
[Bearbeiten]#!/usr/bin/python
import pygame
import sys
def init():
WINWIDTH = 640
WINHEIGHT = 480
pygame.init()
screen = pygame.display.set_mode((WINWIDTH, WINHEIGHT))
screen.fill((200, 200, 200))
pygame.display.update()
def event_loop():
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
sys.exit()
if __name__ == '__main__':
init()
event_loop()
PyGame muss initialisiert werden, das übernimmt die Funktion pygame.init(), welche vor allen anderen PyGame-Funktionen aufgerufen werden muss. Ein neues Fenster erhalten wir mit der Methode pygame.display.set_mode(), der wir neben der Fenstergröße als Tupel auch noch weitere Flags mitgeben könnten. Weitere Angaben wären zum Beispiel, dass wir den Vollbildmodus (pygame.FULLSCREEN) oder OpenGL-Unterstützung (pygame.OPENGL) wünschen. pygame.display.set_mode() übergibt eine so genannte Surface, eine Struktur, die das Grafikfenster repräsentiert.
Den Bildschirm füllen wir mit einer Farbe screen.fill((200, 200, 200)) (grau), die wir als Tupel übergeben. Damit diese Färbung wirksam wird, frischt pygame.display.update() den gesamten Bildschirm auf.
PyGame speichert alle Ereignisse wie Tastendrücke, Mausbewegungen und Joystick-Kommandos in einer Folge von Events. Mit pygame.event.get() holen wir uns das nächste anstehende Ereignis. Im Attribut event.type ist die Art von Ereignis gespeichert, die anliegt. In unserem einfachen Beispiel wird nur nach pygame.QUIT und pygame.KEYDOWN verzweigt, in diesen Fällen beendet sich das Programm. Die nachstehende Tabelle enthält einen Ausschnitt der möglichen Ereignisse, für eine vollständige Liste ziehen Sie bitte die Online-Dokumentation zu Rate.
Ereignis | Bedeutung |
---|---|
QUIT | Anwender wünscht das Programm zu beenden, beispielsweise durch Drücken des Schließen-Knopfes |
KEYDOWN | Taste wurde heruntergedrückt |
KEYUP | heruntergedrückte Taste wurde wieder losgelassen |
MOUSEMOTION | Die Maus wurde bewegt |
MOUSEBUTTONDOWN | Ein Knopf an der Maus wurde gedrückt |
USEREVENT | Ein frei definierbares Ereignis passierte. Genau genommen gibt es hier viele weitere Events, die frei definierbar sind, nämlich alle zwischen USEREVENT und NUMEVENTS-1. |
Malprogramm
[Bearbeiten]Um die Events einmal real zu benutzen, haben wir ein Malprogramm als Beispiel geschrieben. Mit den Tasten 0
bis 3
steuert man die Farbwahl, drückt man einen der Mausknöpfe im Fenster, so wird dort ein Klecks mit der aktuell gewählten Farbe gezeichnet. Die Taste Escape
bricht das Programm ab.
#!/usr/bin/python
import pygame
import sys
class Malprogramm:
def __init__(self, width, height):
self._width = width
self._height = height
pygame.init()
self._screen = pygame.display.set_mode((self._width, self._height))
self._screen.fill((200, 200, 200))
pygame.display.update()
self._drawColor = (200, 0, 0)
def malen(self, (x, y)):
pygame.draw.circle(self._screen, self._drawColor, (x, y), 10)
pygame.display.update((x-10, y-10, 20, 20))
def event_loop(self):
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
sys.exit()
elif event.key == pygame.K_0:
self._drawColor = (255, 255, 255)
elif event.key == pygame.K_1:
self._drawColor = (0, 0, 255)
elif event.key == pygame.K_2:
self._drawColor = (0, 255, 0)
elif event.key == pygame.K_3:
self._drawColor = (255, 0, 0)
elif event.type == pygame.MOUSEBUTTONDOWN:
self.malen(event.pos)
if __name__ == '__main__':
m = Malprogramm(640, 480)
m.event_loop()
Die __init__()-Methode der Klasse verhält sich, wie die Funktion init() aus dem ersten Beispiel, bis auf dass sie eine Startfarbe belegt: self._drawColor = (200, 0, 0) (rot). Diese Farbe wird in der Methode event_loop() bei entsprechenden Tastendrücken verändert.
In der Methode malen() wird eine bestimmte angegeben Stelle mit einem ausgefüllten Kreis bemalt. pygame.draw.circle() benötigt dazu die Surface, die Farbe als Tupel, den Mittelpunkt als Tupel und den Radius. pygame.display.update() frischt den Bildschirm wieder auf, damit wir diesen Kreis sehen können. In diesem Fall benutzen wir eine Variante der Methode, da wir nicht den gesamten Bildschirm bemalt haben, sondern nur ein Rechteck. Dieses den Kreisfleck umgebene Rechteck übergeben wir der Methode pygame.display.update(), die dadurch wesentlich schneller arbeiten kann, als müsste sie den gesamten Bildschirm auffrischen.
Bei gedrückter Taste ist in event.key die Konstante der gedrückten Taste gespeichert. Einen unvollständigen Überblick über die Informationen, die Sie aus der event-Variablen herauslösen können in Abhängigkeit vom gewählten Ereignistyp gibt die folgende Tabelle:
Ereignistyp | Event-Felder | Bedeutung |
---|---|---|
KEYDOWN | unicode | Das Unicode-Zeichen dieses Tastendruckes |
key | K_0..K_9 - Zifferntaste K_a..K_z Buchstabentaste und viele mehr | |
mod | KMOD_LSHIFT - linke Schift-Taste, KMOD_LCTRL linke STRG-Taste KMOD_RALT recht ALT-Taste und viele mehr | |
KEYUP | key, mod | wie oben |
MOUSEMOTION | pos | Absolute Position innerhalb des Fensters als Tupel |
rel | Veränderung zur letzten Position | |
buttons | gedrückte Mausknöpfe | |
MOUSEBUTTONDOWN, MOUSEBUTTONUP | pos, button | wie oben |
Animation
[Bearbeiten]Zu folgendem Beispiel passt der Ausschnitt aus Heinrich Heines Gedicht: Ein Jüngling liebt ein Mädchen:
Ein Jüngling liebt ein Mädchen,
die hat einen andern erwählt;
der andre liebt eine andre,
und hat sich mit dieser vermählt.
Das Mädchen heiratet aus Ärger
den ersten besten Mann,
der ihr in den Weg gelaufen;
der Jüngling ist übel dran.
Wir versuchen einmal eine ähnliche Situation zu programmieren, wobei der Jüngling dem Mädchen hinterherläuft, diese wiederum ihrem Erwählten, der seinerseits einer anderen hinterherläuft. Diese wiederum versucht den Jüngling zu erhaschen. Technisch gesehen lassen wir schlicht einige Punkte sich aufeinander zu bewegen.
Das folgende Programm generiert ein Ereignis (USEREVENT) auf der Basis eines Timers. Der Timer löst das Ereignis nach 200 Millisekunden aus, in der Event-Loop wird entschieden, ob nach weiteren 0,2 Sekunden erneut das gleiche Ereignis erfolgen soll. Nach jedem Zeitintervall werden Linien zwschen Punkten, die sich bewegen, neu gezeichnet:
#!/usr/bin/python
import pygame
import math
import sys
class Animation:
def __init__(self, width, height):
self._width = width
self._height = height
pygame.init()
self._screen = pygame.display.set_mode((self._width, self._height))
self._screen.fill((200, 200, 200))
pygame.display.update()
self._punkte = [(0.0, 0.0), (width - 1.0, 0.0), (width - 1.0, height - 1.0), (0.0, height - 1.0)]
pygame.time.set_timer(pygame.USEREVENT, 200)
def malen(self):
pygame.draw.line(self._screen, (0,0,0), self._punkte[0], self._punkte[1], 1)
pygame.draw.line(self._screen, (0,0,0), self._punkte[1], self._punkte[2], 1)
pygame.draw.line(self._screen, (0,0,0), self._punkte[2], self._punkte[3], 1)
pygame.draw.line(self._screen, (0,0,0), self._punkte[3], self._punkte[0], 1)
pygame.display.update()
def berechnen(self):
punkte_tmp = []
anz_punkte = len(self._punkte)
dx = 0.0 # vorbelegt, da wir den Wert zurueckgeben
for i in xrange(4):
x1, y1 = self._punkte[i]
x2, y2 = self._punkte[(i+1) % anz_punkte]
dx = x2 - x1
dy = y2 - y1
# Einheitsvektor
laenge = math.sqrt(dx * dx + dy * dy)
ex = dx / laenge
ey = dy / laenge
x_neu = x1 + 5.0 * ex
y_neu = y1 + 5.0 * ey
punkte_tmp.append((x_neu, y_neu))
self._punkte = punkte_tmp
return dx
def event_loop(self):
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
sys.exit()
elif event.type == pygame.USEREVENT:
self.malen()
dist = self.berechnen()
if dist > 20.0:
pygame.time.set_timer(pygame.USEREVENT, 200)
if __name__ == '__main__':
a = Animation(800, 800)
a.event_loop()
self._punkte ist eine Liste, die Punkte als Tupel enthält. Diese Punkte werden in der Methode berechnen() neu berechnet und es werden Linien zwischen den Punkten neu gezeichnet. Die Punkte sollen sich dabei aufeinander zubewegen. Der Timer wird in __init__() mit dem Aufruf pygame.time.set_timer() gestartet. Die Argumente sind der Ereignistyp und die Zeitspanne in Millisekunden. Die Methode malen() sorgt dafür, daß zwischen allen vier Punkten Linien in schwarz gemalt werden. Tritt ein USEREVENT ein, so werden die Linien zwischen den Punkten gemalt, es werden neue Punkte berechnet und entschieden, ob weitere Punkte berechnet werden müssen. Gegebenenfalls wird der Timer erneut gestartet.
Die Berechnung erfolgt nach folgendem Schema:
Zuerst werden alle Punkte in den Ecken verteilt. Anschließend bewegt sich jeder Punkt ein Stück auf den anderen zu. Der Punkt self._punkte[0] bewegt sich in die Richtung des Punktes self._punkte[1] und so fort, der letzte bewegt sich wieder auf den ersten Punkt zu. Mathematisch bedeutet das, zu berechnen, wobei der normierte Richtungsvektor zwischen zwei benachbarten Punkten ist. und den nächsten Zeitschritt bedeutet.
Bilder und Fonts
[Bearbeiten]Das folgende Beispiel demonstriert, wie man Bilder in PyGame lädt und mit Hilfe von Fonts einen Text in ihnen aufbringt. Damit das Programm korrekt funktioniert, muss ein Bild mit dem Namen beispiel.png im aktuellen Verzeichnis liegen.
#!/usr/bin/python
import pygame
import sys
WINWIDTH = 640
WINHEIGHT = 480
def init(width, height):
pygame.init()
screen = pygame.display.set_mode((width, height))
screen.fill((250, 250, 250))
pygame.display.update()
return screen
def load_pic(filename, width, height):
surface = pygame.image.load(filename)
picW, picH = surface.get_size()
transform = False
if picW > width:
picH = 1.0 * width / picW * picH
picW = width
transform = True
if picH > height:
picW = 1.0 * height / picH * picW
picH = height
transform = True
if transform:
w = int(round(picW))
h = int(round(picH))
tmp = pygame.transform.scale(surface, (w, h))
surface = tmp
return surface
def blit_pic(surface, pic, width, height):
picW, picH = pic.get_size()
w = (width - picW) / 2
h = (height - picH) / 2
surface.blit(pic, (w, h))
font = pygame.font.SysFont('curier', 50)
text = font.render("Hallo, Welt", True, (0, 0, 200))
picW, picH = text.get_size()
w = (width - picW) / 2
h = (height - picH) / 2
surface.blit(text, (w, h))
pygame.display.update()
def event_loop():
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
sys.exit()
if __name__ == '__main__':
screen = init(WINWIDTH, WINHEIGHT)
pic = load_pic('beispiel.png', WINWIDTH, WINHEIGHT)
blit_pic(screen, pic, WINWIDTH, WINHEIGHT)
event_loop()
Neu an diesem Programm sind die beiden Funktionen load_pic() und blit_pic(). In load_pic() wird ein Bild geladen. Dieses Bild wird repräsentiert durch eine Surface, die sich manipulieren lässt, also beispielsweise drehen, bemalen und skalieren. Die Methode surface.get_size() liefert uns die Größe des Bildes. Das Bild wird so skaliert, dass es auf das Fenster passt.
blit_pic() dient dazu, das Bild auf das Grafikfenster zu zeichnen. Damit es mittig erscheint, wird hier mit Hilfe der Surface-Größe der Rand ausgerechnet.surface.blit(pic, (w, h)) übernimmt das eigentliche Blitten, eine Operation, die eine Surface auf einen anderen kopiert. Der Parameter (w, h) ist hierbei der Ursprung des Bildes.
Ebenfalls mittig soll eine Schrift auf das Bild gebracht werden. Hierzu laden wir mit pygame.font.SysFont() eine Schriftart "Curier" in 50 Punkte Größe. Mit font.render() wird dann eine neue Surface erzeugt, die einen konkreten Text unter Anwendung von Anti-Alias mit der gewählten Farbe repräsentiert. Auch diese wird auf den Bildschirm gezeichnet. Nach beiden Blit-Operationen wird das Grafikfenster aufgefrischt.
Musik
[Bearbeiten]Das folgende Programm spielt WAV und OGG/Vorbis-Dateien, die auf der Kommandozeile übergeben werden ab:
#!/usr/bin/python
import pygame
import sys
import os.path
def hinweis():
print "./sound soundfile"
sys.exit()
if len(sys.argv) != 2:
hinweis()
if not os.path.exists(sys.argv[1]):
hinweis()
pygame.init()
pygame.mixer.music.set_endevent(pygame.USEREVENT)
pygame.mixer.music.load(sys.argv[1])
pygame.mixer.music.play()
while True:
for event in pygame.event.get():
if event.type == pygame.USEREVENT:
sys.exit()
Die Funktion pygame.init() initialisiert auch den Mixer. pygame.mixer.music.set_endevent() legt ein Ereignis fest, welches eintrifft, sobald eine Sound-Datei zu ende gespielt ist. Die auf der Kommandozeile übergebene Sounddatei wird mit pygame.mixer.music.load() geladen, anschließend mit pygame.mixer.music.play() abgespielt.
Mit Hilfe einer zusätzlichen Funktion pygame.mixer.set_volume(lautstärke) könnten wir noch die Lautstärke einstellen. Dieser Wert liegt zwischen 0.0
und 1.0
. Da das Abspielen einer Sounddatei das Programm überdauern kann, müssen wir hier eine Event-Loop einsetzen. Das Programm würde sonst beendet werden, bevor die Datei abgespielt würde. Am Ende bekommen wir das angeforderte Ereignis, das Programm kann sich dann zum richtigen Zeitpunkt beenden.
Zusammenfassung
[Bearbeiten]Dieses Kapitel hat einen Überblick über Möglichkeiten von PyGame geboten. Wir können genau ein Grafikfenster öffnen, zeichnen, auf Ereignisse reagieren und selber Ereignisse erzeugen. Darüber hinaus haben wir gezeigt, wie man Bilder lädt und Texte zeichnet. PyGame kann mehr, als wir hier ausgeführt haben... Experimentieren Sie selbst!