Pygame-Tutorial

Aus Spieleprogrammierer-Wiki
(Unterschied zwischen Versionen)
Wechseln zu: Navigation, Suche
[gesichtete Version][gesichtete Version]
(Ein paar Grundlagen)
(Zusammenbasteln)
 
(39 dazwischenliegende Versionen von 3 Benutzern werden nicht angezeigt)
Zeile 7: Zeile 7:
  
 
=== Wo bekomme ich Pygame? ===
 
=== Wo bekomme ich Pygame? ===
Die offizielle Pygame-Webseite findet sich unter http://www.pygame.org/. Dort gibt es auch immer die jeweils neuste Version, die Dokumentation, Tutorials und vieles mehr. Unter http://www.python.org/ gibt es die Python-Interpreter, und alle, die Python noch nicht kennen, finden dort alles, um es zu lernen.
+
Die offizielle Pygame-Webseite findet sich unter http://www.pygame.org/. Dort gibt es auch immer die jeweils neuste Version, die Dokumentation, Tutorials und vieles mehr. Unter http://www.python.org/ gibt es die Python-Interpreter, und alle, die Python noch nicht kennen, finden dort alles, um es zu lernen. In diesem Tutorial wird Pygame Version 1.9.2 und Python Version 2.7.9 verwendet.
  
 
== Pygame einrichten ==
 
== Pygame einrichten ==
Zeile 18: Zeile 18:
  
 
=== Testen ===
 
=== Testen ===
Ein einfaches Skript zum Testen der Installation:
+
Zuerst erstellen wir ein Python-Projekt: ''File > New > Other > Pydev > Pydev Project''. Jetzt müssen wir einen Namen für das Projekt eingeben, ''Create 'src' folder...'' auswählen und auf ''Finish'' klicken. Danach fragt Eclipse möglicherweise noch, ob wir die ''Pydev Perspective'' öffnen möchten. Dies beantworten wir mit ''Ja'', und schon haben wir ein Python-Projekt angelegt.
 +
 
 +
Fügen wir ein wenig Code hinzu. Dazu machen wir einen Rechtsklick auf den ''src''-Ordner in unserem Projekt links in der Übersicht und klicken auf ''New > Pydev Package''. Dann geben wir einen Namen ein und klicken auf ''Finish''. Pydev hat jetzt automatisch eine <tt>__init__.py</tt> Datei erstellt. Wer möchte, kann diese umbenennen, zum Beispiel in <tt>Teil_1.py</tt>. Die Datei öffnen wir jetzt durch einen Doppelklick und schreiben dieses einfache Skript zum Testen der Installation hinein:
 +
 
 
<sourcecode lang=python line>
 
<sourcecode lang=python line>
 
# -*- coding: UTF-8 -*-
 
# -*- coding: UTF-8 -*-
  
# Import modules
+
# Pygame-Modul importieren.
 
import pygame
 
import pygame
  
if not pygame.font: print('Fehler: pygame.font Modul konnte nicht geladen werden!')
+
# Überprüfen, ob die optionalen Text- und Sound-Module geladen werden konnten.
if not pygame.mixer: print('Fehler: pygame.mixer Modul konnte nicht geladen werden!')
+
if not pygame.font: print('Fehler pygame.font Modul konnte nicht geladen werden!')
 +
if not pygame.mixer: print('Fehler pygame.mixer Modul konnte nicht geladen werden!')
  
# Function main, entry point
 
 
def main():
 
def main():
     # Initializing pygame
+
     # Initialisieren aller Pygame-Module und   
 +
    # Fenster erstellen (wir bekommen eine Surface, die den Bildschirm repräsentiert).
 
     pygame.init()
 
     pygame.init()
 
     screen = pygame.display.set_mode((800, 600))
 
     screen = pygame.display.set_mode((800, 600))
     pygame.display.set_caption("Pygame Tutorial")
+
   
 +
    # Titel des Fensters setzen, Mauszeiger nicht verstecken und Tastendrücke wiederholt senden.
 +
     pygame.display.set_caption("Pygame-Tutorial: Grundlagen")
 
     pygame.mouse.set_visible(1)
 
     pygame.mouse.set_visible(1)
 
     pygame.key.set_repeat(1, 30)
 
     pygame.key.set_repeat(1, 30)
  
     # Initialize clock
+
     # Clock-Objekt erstellen, das wir benötigen, um die Framerate zu begrenzen.
 
     clock = pygame.time.Clock()
 
     clock = pygame.time.Clock()
 
+
   
     # Starting event loop...
+
     # Die Schleife, und damit unser Spiel, läuft solange running == True.
     running = 1
+
     running = True
 
     while running:
 
     while running:
         # Limit framerate
+
         # Framerate auf 30 Frames pro Sekunde beschränken.
 +
        # Pygame wartet, falls das Programm schneller läuft.
 
         clock.tick(30)
 
         clock.tick(30)
  
         # Clear screen...
+
         # screen-Surface mit Schwarz (RGB = 0, 0, 0) füllen.
 
         screen.fill((0, 0, 0))
 
         screen.fill((0, 0, 0))
  
         # Get all events
+
         # Alle aufgelaufenen Events holen und abarbeiten.
 
         for event in pygame.event.get():
 
         for event in pygame.event.get():
             # Close application?
+
             # Spiel beenden, wenn wir ein QUIT-Event finden.
 
             if event.type == pygame.QUIT:
 
             if event.type == pygame.QUIT:
                 running = 0
+
                 running = False
 
+
           
             # Handle keys
+
             # Wir interessieren uns auch für "Taste gedrückt"-Events.
 
             if event.type == pygame.KEYDOWN:
 
             if event.type == pygame.KEYDOWN:
                 # Escape: Quit
+
                 # Wenn Escape gedrückt wird, posten wir ein QUIT-Event in Pygames Event-Warteschlange.
 
                 if event.key == pygame.K_ESCAPE:
 
                 if event.key == pygame.K_ESCAPE:
 
                     pygame.event.post(pygame.event.Event(pygame.QUIT))
 
                     pygame.event.post(pygame.event.Event(pygame.QUIT))
 
+
       
         # Show screen
+
         # Inhalt von screen anzeigen.
 
         pygame.display.flip()
 
         pygame.display.flip()
  
# Check if this is the main file
+
 
 +
# Überprüfen, ob dieses Modul als Programm läuft und nicht in einem anderen Modul importiert wird.
 
if __name__ == '__main__':
 
if __name__ == '__main__':
 +
    # Unsere Main-Funktion aufrufen.
 
     main()
 
     main()
 
</sourcecode>
 
</sourcecode>
Zeile 87: Zeile 96:
 
Als Nächstes initialisieren wir Pygame und erstellen eine Surface (was das ist, besprechen wir später noch), die wir als Bildschirm verwenden, mit einer festen Auflösung von 800x600 Pixeln.
 
Als Nächstes initialisieren wir Pygame und erstellen eine Surface (was das ist, besprechen wir später noch), die wir als Bildschirm verwenden, mit einer festen Auflösung von 800x600 Pixeln.
  
<sourcecode lang=python line start=12>
+
<sourcecode lang=python line start=13>
 
     pygame.init()
 
     pygame.init()
 
     screen = pygame.display.set_mode((800, 600))
 
     screen = pygame.display.set_mode((800, 600))
Zeile 94: Zeile 103:
 
Der <tt>set_mode</tt> Funktion können wir noch zusätzliche Parameter übergeben, aber ohne sie sucht SDL die für den PC besten Einstellungen heraus, was in den meisten Fällen der bessere Weg ist. Wer ein bestimmtes Format benötigt, schaut [http://www.pygame.org/docs/ref/display.html#pygame.display.set_mode hier in der Dokumentation] nach. Das Festlegen des Fenstertitels und das Anzeigen der Maus ist selbsterklärend. Als Letztes schalten wir noch die Tastenwiederholung ein. Dadurch weisen wir Pygame an, wiederholt eine "Taste gedrückt"-Nachricht zu senden, obwohl die Taste noch nicht losgelassen wurde.
 
Der <tt>set_mode</tt> Funktion können wir noch zusätzliche Parameter übergeben, aber ohne sie sucht SDL die für den PC besten Einstellungen heraus, was in den meisten Fällen der bessere Weg ist. Wer ein bestimmtes Format benötigt, schaut [http://www.pygame.org/docs/ref/display.html#pygame.display.set_mode hier in der Dokumentation] nach. Das Festlegen des Fenstertitels und das Anzeigen der Maus ist selbsterklärend. Als Letztes schalten wir noch die Tastenwiederholung ein. Dadurch weisen wir Pygame an, wiederholt eine "Taste gedrückt"-Nachricht zu senden, obwohl die Taste noch nicht losgelassen wurde.
  
<sourcecode lang=python line start=14>
+
<sourcecode lang=python line start=17>
     pygame.display.set_caption("Pygame Tutorial")
+
     pygame.display.set_caption("Pygame-Tutorial: Grundlagen")
 
     pygame.mouse.set_visible(1)
 
     pygame.mouse.set_visible(1)
 
     pygame.key.set_repeat(1, 30)
 
     pygame.key.set_repeat(1, 30)
Zeile 103: Zeile 112:
 
Damit ist Pygame grundlegend initialisiert. Jetzt müssen wir nur noch dafür sorgen, dass das Fenster auch länger als ein Frame angezeigt wird. Dafür verwenden wir eine Nachrichtenschleife. Um Probleme mit der Geschwindigkeit der verschiedenen PCs zu vermeiden, legen wir fest, dass wir maximal 30 Frames pro Sekunde berechnen wollen:
 
Damit ist Pygame grundlegend initialisiert. Jetzt müssen wir nur noch dafür sorgen, dass das Fenster auch länger als ein Frame angezeigt wird. Dafür verwenden wir eine Nachrichtenschleife. Um Probleme mit der Geschwindigkeit der verschiedenen PCs zu vermeiden, legen wir fest, dass wir maximal 30 Frames pro Sekunde berechnen wollen:
  
<sourcecode lang=python line start=19>
+
<sourcecode lang=python line start=22>
 
     clock = pygame.time.Clock()
 
     clock = pygame.time.Clock()
 
+
   
     # Starting event loop...
+
     # Die Schleife, und damit unser Spiel, läuft solange running == True.
 
     running = 1
 
     running = 1
 
     while running:
 
     while running:
         # Limit framerate
+
         # Framerate auf 30 Frames pro Sekunde beschränken.
 +
        # Pygame wartet, falls das Programm schneller läuft.
 
         clock.tick(30)
 
         clock.tick(30)
  
         # Clear screen...
+
         # screen-Surface mit Schwarz (RGB = 0, 0, 0) füllen.
 
         screen.fill((0, 0, 0))
 
         screen.fill((0, 0, 0))
 
</sourcecode>
 
</sourcecode>
  
Zuerst erstellen wir ein <tt>time.Cock</tt>-Objekt, welches sich um die Framerate-Begrenzung kümmern soll. Um die Schleife bequem wieder verlassen zu können, verwenden wir die <tt>running</tt>-Variable. Solange sie 1 ist, bleiben wir in der Schleife. In der Schleife lassen wir das <tt>time.Clock</tt>-Objekt erstmal berechnen, wie lange wir warten müssen, um maximal 30 Frames pro Sekunde zu erhalten. Danach überschreiben wir den gesamten Bildschirm mit der Farbe Schwarz.
+
Zuerst erstellen wir ein <tt>time.Clock</tt>-Objekt, welches sich um die Framerate-Begrenzung kümmern soll. Um die Schleife bequem wieder verlassen zu können, verwenden wir die <tt>running</tt>-Variable. Solange sie 1 ist, bleiben wir in der Schleife. In der Schleife lassen wir das <tt>time.Clock</tt>-Objekt erstmal berechnen, wie lange wir warten müssen, um maximal 30 Frames pro Sekunde zu erhalten. Danach überschreiben wir den gesamten Bildschirm mit der Farbe Schwarz.
  
<sourcecode lang=python line start=31>
+
<sourcecode lang=python line start=35>
 
         for event in pygame.event.get():
 
         for event in pygame.event.get():
             # Close application?
+
             # Spiel beenden, wenn wir ein QUIT-Event finden.
 
             if event.type == pygame.QUIT:
 
             if event.type == pygame.QUIT:
                 running = 0
+
                 running = False
 
</sourcecode>
 
</sourcecode>
  
Zeile 128: Zeile 138:
 
Als Nächstes interessiert uns, welche Tasten der Benutzer gedrückt hat. Diese Events werden durch den Typ <tt>KEYDOWN</tt> repräsentiert.
 
Als Nächstes interessiert uns, welche Tasten der Benutzer gedrückt hat. Diese Events werden durch den Typ <tt>KEYDOWN</tt> repräsentiert.
  
<sourcecode lang=python line start=37>
+
<sourcecode lang=python line start=41>
 
             if event.type == pygame.KEYDOWN:
 
             if event.type == pygame.KEYDOWN:
                 # Escape: Quit
+
                 # Wenn Escape gedrückt wird, posten wir ein QUIT-Event in Pygames Event-Warteschlange.
 
                 if event.key == pygame.K_ESCAPE:
 
                 if event.key == pygame.K_ESCAPE:
 
                     pygame.event.post(pygame.event.Event(pygame.QUIT))
 
                     pygame.event.post(pygame.event.Event(pygame.QUIT))
Zeile 138: Zeile 148:
 
Nachdem wir alle Events durchgesehen haben, können wir endlich den Bildschirminhalt anzeigen:
 
Nachdem wir alle Events durchgesehen haben, können wir endlich den Bildschirminhalt anzeigen:
  
<sourcecode lang=python line start=43>
+
<sourcecode lang=python line start=47>
 
         pygame.display.flip()
 
         pygame.display.flip()
 
</sourcecode>
 
</sourcecode>
Zeile 145: Zeile 155:
 
Damit sind wir auch fast schon am Ende des Beispiels. Das gerade Besprochene haben wir in eine Funktion <tt>main</tt> gesteckt, die wir jetzt einfach aufrufen, falls diese Datei nicht als Modul importiert wird:
 
Damit sind wir auch fast schon am Ende des Beispiels. Das gerade Besprochene haben wir in eine Funktion <tt>main</tt> gesteckt, die wir jetzt einfach aufrufen, falls diese Datei nicht als Modul importiert wird:
  
<sourcecode lang=python line start=46>
+
<sourcecode lang=python line start=51>
 
if __name__ == '__main__':
 
if __name__ == '__main__':
 +
    # Unsere Main-Funktion aufrufen.
 
     main()
 
     main()
 
</sourcecode>
 
</sourcecode>
Zeile 153: Zeile 164:
  
 
== Programmieren einer Tilemap ==
 
== Programmieren einer Tilemap ==
Jetzt werden wir, am praktischen Beispiel einer Tilemap für ein 2D-Spiel, die Pygame Funktionen für das Laden und Anzeigen von Bildern kennenlernen.
+
Jetzt werden wir, am praktischen Beispiel einer Tilemap für ein 2D-Spiel, die Pygame-Funktionen für das Laden und Anzeigen von Bildern kennen lernen.
  
 
=== Beschreibung der Tilemap ===
 
=== Beschreibung der Tilemap ===
Zeile 159: Zeile 170:
 
==== Was ist das? ====
 
==== Was ist das? ====
 
[[Datei:Pygame-Tutorial_Tilemap.png|thumb|right|Screenshot der Tilemap, die in diesem Tutorial programmiert wird.]]
 
[[Datei:Pygame-Tutorial_Tilemap.png|thumb|right|Screenshot der Tilemap, die in diesem Tutorial programmiert wird.]]
Bevor wir uns an das Implementieren der Tilemap machen, klären wir noch ganz kurz was genau das ist und wie wir sie hier in dem Tutorial bauen werden.
+
Bevor wir uns an das Implementieren der Tilemap machen, klären wir noch, was genau das ist, wozu man sie benutzen kann und wie wir sie hier in dem Tutorial bauen werden.
Tilemaps werden häufig in 2D-Spielen verwendet, sie sind eine einfache und schnelle Lösung, eine Landschaft oder ähnliches in einem Spiel darzustellen. Die Landschaft ist dabei aus zumeist gleich großen, quadratischen Kacheln (= Tiles) zusammengesetzt, daher der Name. Mehrere, verschiedene Tile-Grafiken fasst man zu sogenannten Tilesets zusammen, damit kann man zum Beispiel während dem Spiel einfach und schnell den Grafikstiländern (etwa eine normale Gras-Grafik in eine Gras-mit-Schnee-Grafik) ändern. Um Platz zu sparen und um die Übersicht zu erhöhen werden Tiles aus einem Set oftmals in einer Bilddatei gespeichert. Eine Tilemap speichert jetzt einfach eine Liste von Informationen, an welcher Position welches Tile liegt (zum Beispiel über den Namen eines Tiles in einem 2D-Array).
+
Tilemaps werden häufig in 2D-Spielen verwendet. Sie sind eine einfache und schnelle Lösung, eine Landschaft oder ähnliches in einem Spiel darzustellen. Die Landschaft ist dabei aus zumeist gleich großen, quadratischen Kacheln (''Tiles'') zusammengesetzt, daher der Name. Mehrere, verschiedene Tile-Grafiken fasst man zu sogenannten ''Tilesets'' zusammen, damit kann man zum Beispiel während des Spiels einfach und schnell den Grafikstil ändern (etwa eine normale Gras-Grafik in eine Gras-mit-Schnee-Grafik) ändern. Um Platz zu sparen und um die Übersicht zu erhöhen, werden Tiles aus einem Set oftmals in einer einzigen Bilddatei gespeichert. Eine Tilemap speichert nun einfach eine Liste von Informationen, an welcher Position welches Tile liegt (zum Beispiel als 2D-Array, bei dem in jedem Eintrag abgespeichert ist, welcher Tile-Typ dort zu finden ist).
  
 
==== Wie gehen wir vor? ====
 
==== Wie gehen wir vor? ====
Unsere Tilemap wird folgendermaßen arbeiten: Wir verwenden eine Klasse names TileType, welche exakt einen Tile-Typ repräsentiert. Sie speichert die Position dieses Typs auf der Grafik und seinen Namen. Hier ist außerdem der passende Ort, um weitere Informationen, wie etwa
+
Unsere Tilemap wird folgendermaßen arbeiten: Wir verwenden eine Klasse names <tt>TileType</tt>, welche exakt einen Tile-Typ repräsentiert. Sie speichert die Position dieses Typs auf der Tileset-Grafik und seinen Namen. Hier ist außerdem der passende Ort, um weitere Informationen, wie etwa Begehbarkeit, "Tödlichkeit", Ereignisse usw. zu speichern. Die Klasse <tt>Tileset</tt> speichert in einem Dictionary (assoziatives Array) alle Tile-Typen, sowie die einheitliche Größe der Tiles. Und dazu noch die Grafik, auf der sämtliche Tiles vorhanden sind. Außerdem ermöglichen wir es, dass man diese Grafik einfach gegen eine andere austauschen kann. Ein Tile-Typ muss per Hand hinzugefügt werden eine gute Gelegenheit um den Umgang mit den Datei-Funktionen von Python zu lernen ...
Begehbarkeit, Ereignisse usw. zu speichern. Die Klasse Tileset speichert in einem Dictionary (Python-Pendant zu std::map in C++) alle Tile-Typen, sowie die einheitliche Größe der Tiles. Und dazu noch eine Grafik, auf der sämtliche Tiles vorhanden sind. Außerdem ermöglichen wir es, dass man diese Grafik einfach gegen eine Andere tauschen kann. Ein Tile-Typ muss per Hand hinzugefügt werden, eine gute Gelegenheit um den Umgang mit den Datei-Funktionen von Python zu lernen ;)
+
Zu guter Letzt haben wir noch eine Klasse namens <tt>Tilemap</tt>, die sich hauptsächlich um das Anzeigen der Tiles kümmert. Hier wird ein Tileset gespeichert, und natürlich eine Liste von Tiles. Die Liste implementieren wir als 2D-Array, in dem pro Eintrag die ID eines Tile-Types gespeichert ist.
Zu guter Letzt habe wir noch eine Klasse namens Tilemap, die sich hauptsächlich um das Anzeigen der Tiles kümmert. Hier wird ein Tileset gespeichert, und natürlich eine Liste von Tiles. Die Liste verwenden wir als 2D-Array in denen die ID eines Tile-Types gespeichert ist.
+
  
 
=== Bilder in Pygame ===
 
=== Bilder in Pygame ===
  
==== Los gehts ====
+
==== Los geht's ====
 
Bevor wir uns an das Programmieren der Tilemap machen, schauen wir, wie Pygame uns mit den Bildern helfen kann. Mittels Pygame können wir sehr schnell und sehr einfach ein Bild laden:
 
Bevor wir uns an das Programmieren der Tilemap machen, schauen wir, wie Pygame uns mit den Bildern helfen kann. Mittels Pygame können wir sehr schnell und sehr einfach ein Bild laden:
  
Zeile 191: Zeile 201:
  
 
==== Surfaces ====
 
==== Surfaces ====
Pygame speichert alles, was irgendwie angezeigt werden könnte, in so genannten Surfaces. Auch Bilder und der Bildschirm werden als Surface repräsentiert. Für jetzt ist erstmal wichtig, dass eine Surface oder Teile einer Surface auf andere Surfaces bzw. den Bildschirm kopiert werden können. Details dazu kommen später.
+
Pygame speichert alles, was irgendwie angezeigt werden könnte, in so genannten ''Surfaces''. Auch Bilder und der Bildschirm werden als Surface repräsentiert. Für jetzt ist erstmal wichtig, dass eine Surface oder Teile einer Surface auf andere Surfaces bzw. den Bildschirm kopiert werden können. Details dazu kommen später.
 +
 
 +
==== Formatkonvertierung ====
 +
Beim Erstellen der Bildschirm-Surface können wir, falls gewünscht, die Farbtiefe des Fensters angeben, bzw. wird dies von Pygame automatisch erledigt. Wenn wir jetzt ein Bild anzeigen lassen wollen, das nicht in diesem Format gespeichert ist, muss Pygame das Bild bei jedem Anzeige-Vorgang konvertieren – das dauert unter Umständen recht lange. Je mehr Bilder, desto mehr leidet die Performance. Deshalb werden wir direkt nach dem Laden das Bild in das passende Format konvertieren, so dass diese langsame Umwandlung später nicht mehr nötig ist.
 +
Dazu bietet Pygame gleich zwei Funktionen: eine für Bilder mit einem Alpha-Kanal (Transparenzwert pro Pixel) und eine für Bilder ohne. Da es auch eine Funktion gibt, die prüft, ob das Bild einen Alpha-Kanal hat, können wir das ohne Probleme automatisieren:
  
==== Format-Konvertierung ====
 
Beim Erstellen der Bildschirm-Surface können wir, falls gewünscht, die Farbtiefe des Fensters angeben, bzw wird dies von Pygame automatisch erledigt. Wenn wir jetzt ein Bild anzeigen lassen wollen welche nicht in diesem Format gespeichert ist, muss Pygame das Bild bei jedem Anzeige-Vorgang konvertieren - und das dauert unter Umständen recht lange, je mehr Bilder desto mehr leidet die Performance. Deshalb werden wir direkt nach dem Laden das Bild in das passende Format konvertieren.
 
Dazu bietet Pygame gleich zwei Funktionen, eine für Bilder mit einem Alpha-Kanal und eine für Bilder ohne und da es auch eine Funktion gibt die prüft ob das Bild einen Alpha-Kanal hat, können wir das ohne Probleme automatisieren:
 
 
<sourcecode lang=python>
 
<sourcecode lang=python>
if image.get_alpha() == None:
+
if image.get_alpha() is None:
 
     image = image.convert()
 
     image = image.convert()
 
else:
 
else:
Zeile 203: Zeile 214:
 
</sourcecode>
 
</sourcecode>
  
==== Und was ist mit Kreisen? ====
+
==== Alles nur rechteckig? ====
Wer die bisherige Ladefunktion schon getestet hat, wird festgestellt haben, dass wir immer nur Rechtecke anzeigen können. Das ist für ein echtes Spiel natürlich zuwenig da die wenigsten Figuren rechteckig aussehen ;) Um dieses Problem zu lösen bietet Pygame natürlich die bekannte Colorkey-Methode, in der eine bestimmte Farbe beim Anzeigen eines Bildes einfach ausgelassen wird. Das festlegen des Colorkeys unter Pygame ist auch sehr einfach realisierbar:
+
Wer die bisherige Ladefunktion schon getestet hat, wird festgestellt haben, dass wir immer nur Rechtecke anzeigen können. Das ist für ein echtes Spiel natürlich zu wenig, da die wenigsten Figuren rechteckig aussehen. Um dieses Problem zu lösen, bietet Pygame natürlich die bekannte Colorkey-Methode, in der eine bestimmte Farbe beim Anzeigen eines Bildes einfach ausgelassen wird. Das festlegen des Colorkeys unter Pygame ist auch sehr einfach realisierbar:
 +
 
 
<sourcecode lang=python>
 
<sourcecode lang=python>
 
image.set_colorkey((rot, grün, blau), pygame.RLEACCEL)
 
image.set_colorkey((rot, grün, blau), pygame.RLEACCEL)
 
</sourcecode>
 
</sourcecode>
Sehr oft wird als Colorkey die Farbe (255, 0, 255) verwendet, da man sie selten in einem Bild benötigt.
 
  
==== Zusammengesetzt: Ein Bild laden ====
+
Sehr oft wird als Colorkey die Farbe Magenta (255, 0, 255) verwendet, da man sie selten in einem Bild benötigt.
Und hier nochmal die gesamte Funktion zum laden eines Bildes mit Pygame:
+
 
<sourcecode lang=python line>
+
==== Zusammengesetzt: ein Bild laden ====
def loadImage(filename, colorkey = None):
+
Und hier nochmal die gesamte Funktion zum Laden eines Bilds mit Pygame:
 +
 
 +
<sourcecode lang=python line start=5>
 +
# Hilfsfunktion, um ein Bild zu laden:
 +
def loadImage(filename, colorkey=None):
 +
    # Pygame das Bild laden lassen.
 
     image = pygame.image.load(filename)
 
     image = pygame.image.load(filename)
  
     if image.get_alpha() == None:
+
    # Das Pixelformat der Surface an den Bildschirm (genauer: die screen-Surface) anpassen.
 +
    # Dabei die passende Funktion verwenden, je nach dem, ob wir ein Bild mit Alpha-Kanal haben oder nicht.
 +
     if image.get_alpha() is None:
 
         image = image.convert()
 
         image = image.convert()
 
     else:
 
     else:
 
         image = image.convert_alpha()
 
         image = image.convert_alpha()
  
 +
    # Colorkey des Bildes setzen, falls nicht None.
 +
    # Bei -1 den Pixel im Bild an Position (0, 0) als Colorkey verwenden.
 
     if colorkey is not None:
 
     if colorkey is not None:
 
         if colorkey is -1:
 
         if colorkey is -1:
Zeile 228: Zeile 248:
 
     return image
 
     return image
 
</sourcecode>
 
</sourcecode>
Wir haben noch einen kleinen Zusatz eingebaut: Wenn der colorkey auf -1 gesetzt ist verwenden wir automatisch die Farbe des Pixels an Position (0,0) als Colorkey, und wenn der Colorkey auf None gesetzt ist (was standardmäßig der Fall ist) wird überhaupt kein Colorkey gesetzt.
+
 
 +
Wir haben noch einen kleinen Zusatz eingebaut: Wenn der Colorkey auf -1 gesetzt ist, verwenden wir automatisch die Farbe des Pixels an Position (0, 0) als Colorkey, und wenn der Colorkey auf <tt>None</tt> gesetzt ist (was standardmäßig der Fall ist), dann wird überhaupt kein Colorkey gesetzt.
  
 
==== Bild anzeigen ====
 
==== Bild anzeigen ====
Ein Bild können wir, wie bereits erwähnt, entweder direkt auf die Bildschirm rendern oder auf ein andere Surface, diese Methode nennt sich Bit Block Transfer. Jede Surface besitzt eine Funktion namens blit mit der eine andere Surface auf sie kopiert werden kann.
+
Ein Bild können wir, wie bereits erwähnt, entweder direkt auf den Bildschirm rendern oder auf ein andere Surface. Diese Methode nennt sich ''Bit Block Transfer''. Jede Surface besitzt eine Funktion namens <tt>blit</tt>, mit der eine andere Surface auf sie kopiert werden kann.
In der Standard-Variante wird eine komplette Surface an eine bestimmte Position geblittet:
+
In der Standardvariante wird eine komplette Surface an eine bestimmte Position geblittet:
 +
 
 
<sourcecode lang=python>
 
<sourcecode lang=python>
 
screen.blit(image, (0, 10))
 
screen.blit(image, (0, 10))
 
</sourcecode>
 
</sourcecode>
Damit wird die Surface _image an der Position x=0 und y=10 auf die Surface screen geblittet (screen ist hier die Surface, die zum Anzeigen auf dem Bildschirm verwendet wird).
+
 
 +
Damit wird die Surface <tt>image</tt> an der Position (0, 10) auf die Surface <tt>screen</tt> geblittet (<tt>screen</tt> ist hier die Surface, die zum Anzeigen auf dem Bildschirm verwendet wird).
  
 
==== Mehr zu Surfaces ====
 
==== Mehr zu Surfaces ====
Jetzt können wir Bilder laden und optimieren und anzeigen. Wem das reicht, der kann gleich zum nächsten Abschnitt weitergehen. Alle anderen bekommen jetzt noch ein paar Details zu Surfaces (http://www.pygame.org/docs/ref/surface.html). In den meisten Fällen erstellt man eine Surface durch das Laden eines Bildes. Falls nötig, kann man aber auch eine "per Hand" erstellen (etwa um einen Buffer zu haben). Dafür stehen zwei Funktionen zur Verfügung:
+
Jetzt können wir Bilder laden, optimieren und anzeigen. Wem das reicht, der kann gleich zum nächsten Abschnitt springen. Alle Anderen bekommen jetzt noch ein paar Details zu Surfaces (und noch viel mehr gibt's [http://www.pygame.org/docs/ref/surface.html hier]). In den meisten Fällen erstellt man eine Surface durch das Laden eines Bilds. Falls nötig, kann man aber auch eine "per Hand" erstellen (etwa um einen Puffer zu haben). Dafür stehen zwei Funktionen zur Verfügung:
  
 
<sourcecode lang=python>
 
<sourcecode lang=python>
Zeile 246: Zeile 269:
 
</sourcecode>
 
</sourcecode>
  
Als Flags können wir "anfragen" (das heißt, unter Umständen ignoriert SDL diese Anfrage, wenn es Probleme geben könnte), dass die Surface im Grafikkartenspeicher abgelegt werden soll (Flag <tt>HWSURFACE</tt>) und/oder dass wir "per-pixel Alpha"-Unterstützung wollen (Flag <tt>SRCALPHA</tt>).
+
Als Flags können wir "anfragen" (das heißt, unter Umständen ignoriert SDL diese Anfrage, wenn es Probleme geben könnte), dass die Surface im Grafikkartenspeicher abgelegt werden soll (Flag <tt>HWSURFACE</tt>) und/oder dass wir einen Alpha-Kanal haben möchten (Flag <tt>SRCALPHA</tt>).
Letzteres ist nicht empfohlen, da es langsam ist. In den meinsten Fällen ist es aber zum Glück nicht nötig, da man ja Colorkeys und einen Transparenzwert für die komplette Surface angeben kann.
+
Letzteres könnte langsam sein. In den meinsten Fällen ist es aber zum Glück nicht nötig, da man Colorkeys und einen Transparenzwert für die komplette Surface angeben kann.
Die wichtigste Funktion einer Surface ist wohl die <tt>blit</tt>-Funktion (http://www.pygame.org/docs/ref/surface.html#Surface.blit). Sie ermöglicht es, andere Surfaces (bzw. Ausschnitte daraus) auf diese zu kopieren:
+
Die wichtigste Funktion einer Surface ist wohl die <tt>blit</tt>-Funktion ([http://www.pygame.org/docs/ref/surface.html#Surface.blit Dokumentation]), die wir schon kurz angeschaut haben. Sie ermöglicht es, andere Surfaces (bzw. Ausschnitte daraus) auf eine andere zu kopieren:
  
 
<sourcecode lang=python>
 
<sourcecode lang=python>
Zeile 254: Zeile 277:
 
</sourcecode>
 
</sourcecode>
  
Der Parameter <tt>source</tt> muss eine weitere Surface sein die kopiert werden soll. Über den zweiten Parameter, <tt>dest</tt>, kann der Zielbereich angegeben werden, in den kopiert wird. Entweder als ein Tupel von Zahlen <tt>(x, y)</tt> oder als eine <tt>Rect</tt>-Variable. Um nur Ausschnitte von einer Surface zu kopieren (unerlässlich für unsere Tilemap), benötigt man den dritten Parameter, <tt>area</tt>. Er erwartet eine <tt>Rect</tt>-Variable, die den kleineren Ausschnit definiert. Über den <tt>special_flags</tt>-Parameter kann man Überblendungseffekte beim Kopieren einbauen (<tt>BLEND_ADD</tt>, <tt>BLEND_SUB</tt>, <tt>BLEND_MULT</tt>, <tt>BLEND_MIN</tt>, <tt>BLEND_MAX</tt>).
+
Der Parameter <tt>source</tt> ist die Surface, die ganz oder teilweise kopiert werden soll. Über den zweiten Parameter, <tt>dest</tt>, kann der Zielbereich angegeben werden, in den kopiert wird. Entweder als ein Tupel von Zahlen <tt>(x, y)</tt> oder als eine <tt>Rect</tt>-Variable. Um nur Ausschnitte von einer Surface zu kopieren (unerlässlich für unsere Tilemap), benötigt man den dritten Parameter, <tt>area</tt>. Er erwartet eine <tt>Rect</tt>-Variable, die den kleineren Ausschnit definiert. Über den <tt>special_flags</tt>-Parameter kann man Überblendungseffekte beim Kopieren einbauen (<tt>BLEND_ADD</tt>, <tt>BLEND_SUB</tt>, <tt>BLEND_MULT</tt>, <tt>BLEND_MIN</tt>, <tt>BLEND_MAX</tt>).
 
Weitere wichtige Funktionen sind <tt>get_width</tt> und <tt>get_height</tt> zum Ermitteln der Größe der Surface, sowie <tt>fill</tt>, um die Surface mit einer Farbe komplett zu füllen.
 
Weitere wichtige Funktionen sind <tt>get_width</tt> und <tt>get_height</tt> zum Ermitteln der Größe der Surface, sowie <tt>fill</tt>, um die Surface mit einer Farbe komplett zu füllen.
  
Zeile 262: Zeile 285:
 
Hinweis: Die Zeilennummern stimmen mit den fertigen Dateien überein, den gesamten Code findest du am Ende dieses Teiles.
 
Hinweis: Die Zeilennummern stimmen mit den fertigen Dateien überein, den gesamten Code findest du am Ende dieses Teiles.
  
==== Die Klasse: TileType ====
+
==== Die Klasse TileType ====
 
Dies ist die einfachste und kleinste Klasse. Deshalb zuerst der Code:
 
Dies ist die einfachste und kleinste Klasse. Deshalb zuerst der Code:
 +
 
<sourcecode lang=python line start=5>
 
<sourcecode lang=python line start=5>
 +
# Speichert die Daten eines Tile-Typs:
 
class TileType(object):
 
class TileType(object):
     def __init__(self, name, startX, startY, width, height):
+
    # Im Konstruktor speichern wir den Namen
         self.__name = name
+
    # und erstellen das Rect (den Bereich) dieses Typs auf der Tileset-Grafik.
         self.__rect = pygame.rect.Rect(startX, startY, width, height)
+
     def __init__(self, name, start_x, start_y, width, height):
 +
         self.name = name
 +
         self.rect = pygame.rect.Rect(start_x, start_y, width, height)
 +
</sourcecode>
  
    def getName(self):
+
Wir speichern als erstes den Namen bzw. die ID des Tile-Typs in der Variable <tt>name</tt>.
        return self.__name
+
In einem <tt>Rect</tt> speichern wir gleichzeitig die Position und die Größe dieses Types in der Tileset-Grafik.
 +
Wir gehen gleich zur nächsten Klasse weiter, der <tt>Tileset</tt>-Klasse, in der deutlich mehr passiert.
  
    def getRect(self):
+
==== Die Klasse Tileset ====
        return self.__rect
+
Hier in der <tt>Tileset</tt>-Klasse müssen wir vor allem die Tile-Typen speichern. Der Konstruktor der Klasse erwartet einen Dateinamen für das Bild der Tiles, dazu einen Colorkey für das Bild sowie die Breite und Höhe eines Tiles (alle Tiles haben die gleiche Größe).
</sourcecode>
+
Zuletzt wird noch ein Dictionary für die Tile-Typen angelegt, das aber erst einmal leer bleibt.
Wir speichern als erstes den Namen, bze die ID des Types in der privaten Variable __name, über die Funktion getName können wir ihn wieder auslesen.
+
In einem Rect speichern wir gleichzeitig die Position und die Größe dieses Types.
+
Wir gehen gleich zur nächsten Klasse weiter, der Tileset-Klasse in der deutlich mehr passiert.
+
  
==== Die Klasse: Tileset ====
+
<sourcecode lang=python line start=7>
Hier in der Tileset-Klasse müssen wir vor allem die Tile-Typen speichern. Der Konstruktor der Klasse erwartet einen Dateinamen für das Bild der Tiles, dazu einen Colorkey für das Bild und die Breite und Höhe eines Tiles (alle Tiles haben die gleiche Größe).
+
# Verwaltet die Tileset Grafik und eine Liste mit Tile-Typen.
Zuletzt wird noch ein Dictionary für die Tile-Typen angelegt, der erstmal leer bleibt.
+
<sourcecode lang=python line start=23>
+
 
class Tileset(object):
 
class Tileset(object):
     def __init__(self, image, colorkey, tileWidth, tileHeight):
+
    # Im Konstruktor laden wir die Grafik
         self.__image = loadImage(image, colorkey)
+
    # und erstellen ein leeres Dictionary für die Tile-Typen.
         self.__tileWidth = tileWidth
+
     def __init__(self, image, colorkey, tile_width, tile_height):
         self.__tileHeight = tileHeight
+
         self.image = Utils.load_image(image, colorkey)
 
+
         self.tile_width = tile_width
         self.__tileTypes = dict()
+
         self.tile_height = tile_height
 +
         self.tile_types = dict()
 
</sourcecode>
 
</sourcecode>
Um das Bild anzeigen zu können, bzw Ausschnitte daraus, erstellen wir eine Funktion die das Bild zurückliefert. Außerdem ermöglichen wir es, das Bild gegen eine neues auszutauschen. Wenn während dem Spiel so ein Tausch des Tileset-Bildes öfter vorkommen soll, ist es aber ratsamer, alle Bilder zum Beispiel in einer Liste zu speichern und nicht jedesmal neu zu laden.
 
<sourcecode lang=python line start=31>
 
    def getImage(self):
 
        return self.__image
 
  
    def setImage(self, image, colorkey):
+
Die <tt>loadImage</tt>-Funktion haben wir in das Modul <tt>Utils</tt> ausgelagert. Also das <tt>import Utils</tt> am Anfang des Moduls nicht vergessen.
        self.__image = loadImage(image, colorkey)
+
</sourcecode>
+
Ebenfalls sehr einfach sind die drei Funktionen um die Größe der Tiles zu bekommen:
+
<sourcecode lang=python line start=37>
+
    def getTileWidth(self):
+
        return self.__tileWidth
+
  
    def getTileHeight(self):
+
Kommen wir zur wichtigsten Funktion: Das Hinzufügen eines Tile-Typen. Das ist im Grunde auch nicht sonderlich kompliziert. Wir fügen einfach eine neue Instanz der <tt>TileType</tt>-Klasse in unser Dictionary ein, mit passenden Parametern:
        return self.__tileHeight
+
  
    def getTileSize(self):
+
<sourcecode lang=python line start=17>
        return (self.__tileWidth, self.__tileHeight)
+
    # Neuen Tile-Typ hinzufügen.
</sourcecode>
+
     def add_tile(self, name, start_x, start_y):
Kommen wir zur wichtigsten Funktion: Das Hinzufügen eines Tile-Typen. Das ist im Grunde auch nicht sonderlich kompliziert, wir fügen einfach eine neue Instanz der TileType Klasse in unserem Dictionary hinzu, mit passenden Parametern:
+
         self.tile_types[name] = TileType.TileType(name, start_x, start_y, self.tile_width, self.tile_height)
<sourcecode lang=python line start=46>
+
     def addTile(self, name, startX, startY):
+
         self.__tileTypes[name] = TileType.TileType(name, startX, startY, self.__tileWidth, self.__tileHeight)
+
 
</sourcecode>
 
</sourcecode>
Auch das rausholen eines Types ist leicht, einfach über den Key den passenden Wert zurückliefern:
+
 
<sourcecode lang=python line start=49>
+
Auch das Abfragen eines Types ist leicht, einfach über den Key den passenden Wert zurückliefern:
     def getTile(self, name):
+
 
 +
<sourcecode lang=python line start=21>
 +
    # Versuchen, einen Tile-Type über seinen Namen in der Liste zu finden.
 +
    # Falls der Name nicht existiert, geben wir None zurück.
 +
     def get_tile(self, name):
 
         try:
 
         try:
             return self.__tileTypes[name]
+
             return self.tile_types[name]
 
         except KeyError:
 
         except KeyError:
 
             return None
 
             return None
 
</sourcecode>
 
</sourcecode>
Existiert dieser Schlüssel nicht: Wir fangen die mögliche Exception, die geworfen wird wenn ein Schlüssel übergeben wird der nicht existiert ab, und geben dann einfach None zurück.
 
Damit kommen wir zu der eigentlichen Tilemap-Klasse.
 
  
==== Die Klasse: Tilemap ====
+
Möglicherweise existiert der angegebene Schlüssel nicht. Wir fangen die Exception ab und geben dann einfach <tt>None</tt> zurück.
 +
Damit kommen wir zu der eigentlichen <tt>Tilemap</tt>-Klasse.
 +
 
 +
==== Die Klasse Tilemap ====
 
Wir beginnen wieder mit dem Konstruktor:
 
Wir beginnen wieder mit dem Konstruktor:
 
[[Datei:Pygame-Tutorial_Tileset.png|thumb|right|Die verwendete Tileset-Grafik.]]
 
[[Datei:Pygame-Tutorial_Tileset.png|thumb|right|Die verwendete Tileset-Grafik.]]
 +
 
<sourcecode lang=python line start=7>
 
<sourcecode lang=python line start=7>
 +
# Die Tilemap Klasse verwaltet die Tile-Daten, die das Aussehen der Karte beschreiben.
 
class Tilemap(object):
 
class Tilemap(object):
     def __init__(self):      
+
     def __init__(self):
         self.__tileset = Tileset.Tileset("tileset.png", (255, 0, 255), 32, 32)
+
        # Wir erstellen ein neues Tileset.
         self.__tileset.addTile("grass", 0, 0)
+
        # Hier im Tutorial fügen wir manuell vier Tile-Typen hinzu.
         self.__tileset.addTile("mud", 32, 0)
+
         self.tileset = Tileset.Tileset("tileset.png", (255, 0, 255), 32, 32)
         self.__tileset.addTile("water", 64, 0)
+
         self.tileset.add_tile("grass", 0, 0)
         self.__tileset.addTile("block", 0, 32)
+
         self.tileset.add_tile("mud", 32, 0)
 +
         self.tileset.add_tile("water", 64, 0)
 +
         self.tileset.add_tile("block", 0, 32)
 
</sourcecode>
 
</sourcecode>
Es fällt natürlich sofort auf, dass das ziemlich unflexibel ist. Ein deutlich besserer Weg wäre, sämtliche Informationen aus einer Datei zu lesen. Aufgrund der Einfachheit dieses Tutorials werden wir aber davon absehen und überlassen dem Leser die Verbesserung!
+
 
 +
Es fällt natürlich sofort auf, dass das ziemlich unflexibel ist. Ein deutlich besserer Weg wäre es, sämtliche Informationen aus einer Datei zu lesen. Aufgrund der Einfachheit dieses Tutorials werden wir aber davon absehen und überlassen dem Leser die Verbesserung!
 
Was passiert: Wir erstellen eine neue Instanz der <tt>Tileset</tt>-Klasse, übergeben ein passendes Bild, den Colorkey und die Größe eines einzelne Tiles (32x32 Pixel). Danach fügen wir per Hand vier Tile-Typen hinzu.
 
Was passiert: Wir erstellen eine neue Instanz der <tt>Tileset</tt>-Klasse, übergeben ein passendes Bild, den Colorkey und die Größe eines einzelne Tiles (32x32 Pixel). Danach fügen wir per Hand vier Tile-Typen hinzu.
<sourcecode lang=python line start=15>
 
        self.__cameraX = 0
 
        self.__cameraY = 0
 
           
 
        self.__width = 10
 
        self.__height = 10
 
  
         self.__tiles = list()
+
<sourcecode lang=python line start=18>
 +
        # Festlegen der Startposition der Kamera. Hier (0, 0).
 +
        self.camera_x = 0
 +
        self.camera_y = 0
 +
       
 +
        # Die Größe der Maps in Tiles.
 +
        self.width = 30
 +
        self.height = 25
 +
 
 +
        # Erstellen einer leeren Liste für die Tile Daten.
 +
         self.tiles = list()
 
</sourcecode>
 
</sourcecode>
 +
 
Wir legen Standardwerte für die Position der Kamera und die Größe der Karte fest. Die Größe der Karten wird in Tiles angegeben, nicht in Pixeln. Auch hier gilt wieder: Die Größe kann flexibler gestaltet werden, etwa durch einen Parameter in dem Konstruktor oder eine Datei. Zudem erstellen wir eine noch leere Liste, in der wir später die Tiles speichern wollen.
 
Wir legen Standardwerte für die Position der Kamera und die Größe der Karte fest. Die Größe der Karten wird in Tiles angegeben, nicht in Pixeln. Auch hier gilt wieder: Die Größe kann flexibler gestaltet werden, etwa durch einen Parameter in dem Konstruktor oder eine Datei. Zudem erstellen wir eine noch leere Liste, in der wir später die Tiles speichern wollen.
<sourcecode lang=python line start=23>
+
 
         for i in range(0, self.__width):
+
<sourcecode lang=python line start=29>
             self.__tiles.append(list())
+
        # Manuelles Befüllen der Tile-Liste:
             for j in range(0, self.__height):
+
        # Jedes Feld bekommt ein zufälliges Tile zugewiesen.
 +
         for i in range(0, self.height):
 +
             self.tiles.append(list())
 +
             for j in range(0, self.width):
 
                 x = random.randint(0, 4)
 
                 x = random.randint(0, 4)
 
                 if x == 0:
 
                 if x == 0:
                     self.__tiles[i].append("grass")
+
                     self.tiles[i].append("grass")
 
                 elif x == 1:
 
                 elif x == 1:
                     self.__tiles[i].append("water")
+
                     self.tiles[i].append("water")
 
                 elif x == 2:
 
                 elif x == 2:
                     self.__tiles[i].append("mud")
+
                     self.tiles[i].append("mud")
 
                 else:
 
                 else:
                     self.__tiles[i].append("block")
+
                     self.tiles[i].append("block")
 
</sourcecode>
 
</sourcecode>
Mit diesem Code endet der Konstruktor. Wir befüllen hier die Liste der Tiles. Dazu verwendenwir zwei <tt>for</tt>-Schleifen, die die Breite bzw. Höhe der Karte durchlaufen. Für jedes <tt>i</tt> (= eine Zeile) erstellen wir eine neue Liste, in der wir die Spalten mit einem Zufallswert füllen (hier gibt es eine Auswahl zwischen den vier hinzugefügten Tiles). Standardmäßig sollte hier jedes Feld auf <tt>""</tt> oder <tt>None</tt> gesetzt werden, oder man liest gleich alle Positionsdaten aus einer Datei.
+
 
Schauen wir uns die komplizierteste, aber auch wichtigste Funktion an: Die Funktion, die die Karte auf den Bildschirm (oder eine beliebige andere Surface rendert):
+
Mit diesem Code endet der Konstruktor. Wir befüllen hier die Liste der Tiles. Dazu verwenden wir zwei <tt>for</tt>-Schleifen, die die Höhe bzw. Breite der Karte durchlaufen. Für jedes <tt>i</tt> (eine Zeile) erstellen wir eine neue Liste, die wir mit Zufallswerten füllen (hier gibt es eine Auswahl zwischen den vier hinzugefügten Tiles). Standardmäßig sollte hier jedes Feld auf <tt>""</tt> oder <tt>None</tt> gesetzt werden, oder man liest gleich alle Positionsdaten aus einer Datei.
<sourcecode lang=python line start=37>
+
Schauen wir uns die komplizierteste, aber auch wichtigste Funktion an: Die Funktion <tt>render</tt>, die die Karte auf den Bildschirm (oder eine beliebige andere Surface rendert):
 +
 
 +
<sourcecode lang=python line start=45>
 +
    # Hier rendern wir den sichtbaren Teil der Karte.
 
     def render(self, screen):
 
     def render(self, screen):
         for y in range(0, int(screen.get_height() / self.__tileset.getTileHeight()) + 1):
+
        # Zeilenweise durch die Tiles durchgehen.
             ty = y + self.__cameraY
+
         for y in range(0, int(screen.get_height() / self.tileset.tile_height) + 1):
             if ty >= self.__height or ty < 0:
+
            # Die Kamera Position mit einbeziehen.
 +
             ty = y + self.camera_y
 +
             if ty >= self.height or ty < 0:
 
                 continue
 
                 continue
             col = self.__tiles[ty]
+
             # Die aktuelle Zeile zum einfacheren Zugriff speichern.
             for x in range(0, int(screen.get_width() / self.__tileset.getTileWidth()) + 1):
+
            line = self.tiles[ty]
                 tx = x + self.__cameraX
+
            # Und jetzt spaltenweise die Tiles rendern.
                 if tx >= self.__width or tx < 0:
+
             for x in range(0, int(screen.get_width() / self.tileset.tile_width) + 1):
 +
                # Auch hier müssen wir die Kamera beachten.
 +
                 tx = x + self.camera_x
 +
                 if tx >= self.width or tx < 0:
 
                     continue
 
                     continue
                 tilename = col[tx]
+
                # Wir versuchen, die Daten des Tiles zu bekommen.
                 tile = self.__tileset.getTile(tilename)
+
                 tilename = line[tx]
 +
                 tile = self.tileset.get_tile(tilename)
 +
                # Falls das nicht fehlschlägt können wir das Tile auf die screen-Surface blitten.
 
                 if tile is not None:
 
                 if tile is not None:
                     screen.blit(self.__tileset.getImage(), (x * self.__tileset.getTileWidth(), y * self.__tileset.getTileHeight()), tile.getRect())
+
                     screen.blit(self.tileset.image, (x * self.tileset.tile_width, y * self.tileset.tile_height), tile.rect)
 
</sourcecode>
 
</sourcecode>
 +
 
Wir werden die Funktion Zeile für Zeile durchgehen:
 
Wir werden die Funktion Zeile für Zeile durchgehen:
  
# Wir wollen nur soviele Tiles rendern, wie auf den Bildschirm passen. Um das herauszufinden, dividieren wir einfach die Höhe des Bildschirms durch die Höhe der Tiles. Um schwarze Rahmen zu vermeiden, addieren wir noch 1 hinzu.
+
* Zeile 48: Wir wollen nur so viele Tiles rendern, wie auf den Bildschirm passen. Um das herauszufinden, dividieren wir einfach die Höhe des Bildschirms durch die Höhe der Tiles. Um schwarze Rahmen zu vermeiden, addieren wir noch 1 hinzu.
# Die y-Position auf dem Bildschirm ist nicht gleich der Position in unserer Tile-Liste, diese Position speichern wir in der Variable <tt>ty</tt>. Um den korrekten Wert zu erhalten, müssen wir nur die Kameraposition hinzuaddieren.
+
* Zeile 50: Die y-Position auf dem Bildschirm ist nicht gleich der Position in unserer Tile-Liste, diese Position speichern wir in der Variable <tt>ty</tt>. Um den korrekten Wert zu erhalten, müssen wir nur die Kameraposition hinzuaddieren.
# Wir testen, ob wir uns überhaupt noch innerhalb der Map-Grenze befinden. Wenn nicht, überspringen wir dieses Tile.
+
* Zeile 51: Wir testen, ob wir uns überhaupt noch innerhalb der Map-Grenze befinden. Wenn nicht, überspringen wir das aktuelle Tile.
# Die Tiles werden spaltenweise gerendert, hier speichern wir die aktuelle Spalte.
+
* Zeile 54: Die Tiles werden gleich spaltenweise gerendert, hier speichern wir die aktuelle Zeile.
# In der zweiten <tt>for</tt>-Schleife durchlaufen wie die Breite des Bildschrims.
+
* Zeile 56: In der zweiten <tt>for</tt>-Schleife durchlaufen wie die Breite des Bildschrims.
# Auch die x-Position wird berechnet, genau wie die y-Position.
+
* Zeile 58: Auch die x-Position wird berechnet, genau wie die y-Position.
# Natürlich muss auch hier getestet werden, ob wir die Grenzen der Map einhalten.
+
* Zeile 59: Natürlich muss auch hier getestet werden, ob wir die Grenzen der Map einhalten.
# Als Nächstes holen wir uns den Namen des Tiles, welches wir rendern wollen aus der aktuellen Spalte.
+
* Zeile 62: Als Nächstes holen wir uns den Namen des Tiles, das wir rendern wollen, aus der aktuellen Zeile.
# Über das Tileset erhalten wir das Tile, oder <tt>None</tt>, wenn keines existiert.
+
* Zeile 63: Über das Tileset erhalten wir das Tile, oder <tt>None</tt>, wenn keines existiert.
# Und wenn wir ein Tile haben, wird dieses auf den Bildschrim gerendert.
+
* Zeile 65: Und wenn wir ein Tile haben, wird dieses auf den Bildschrim gerendert.
# In der Tilemap ist das Bild mit allen Tiles gespeichert, das ist unsere Quelle für die <tt>blit</tt>-Funktion. Die Positionen multiplizieren wir noch mit der Größe der Tiles, um keine Überschneidung mit dem vorherigen Tile zu bekommen. Zuletzt der wichtigste Parameter: Der Ausschnitt, der von dem Bild verwendet werden soll, ist in der Tile-Klasse gespeichert.
+
* Zeile 66: In der Tilemap ist das Bild mit allen Tiles gespeichert, das ist unsere Quelle für die <tt>blit</tt>-Funktion. Die Positionen multiplizieren wir noch mit der Größe der Tiles, um keine Überschneidung mit dem vorherigen Tile zu bekommen. Zuletzt der wichtigste Parameter: Der Ausschnitt, der von dem Bild verwendet werden soll, ist in der Tile-Klasse gespeichert.
  
 
Damit sind wir fast fertig.
 
Damit sind wir fast fertig.
 
Die Veränderung der Kameraposition ist im Gegensatz zur letzten Funktion sehr einfach:
 
Die Veränderung der Kameraposition ist im Gegensatz zur letzten Funktion sehr einfach:
<sourcecode lang=python line start=53>
+
 
     def handleInput(self, key):
+
<sourcecode lang=python line start=69>
 +
    # Tastendrücke verarbeiten:
 +
     def handle_input(self, key):
 +
        # Pfeiltaste links oder rechts erhöht bzw. verringert die x-Position der Kamera.
 
         if key == pygame.K_LEFT:
 
         if key == pygame.K_LEFT:
             self.__cameraX += 1
+
             self.camera_x += 1
 
         if key == pygame.K_RIGHT:
 
         if key == pygame.K_RIGHT:
             self.__cameraX -= 1
+
             self.camera_x -= 1
 
+
       
 +
        # Und das gleiche nochmal für die y-Position.
 
         if key == pygame.K_UP:
 
         if key == pygame.K_UP:
             self.__cameraY += 1
+
             self.camera_y += 1
 
         if key == pygame.K_DOWN:
 
         if key == pygame.K_DOWN:
             self.__cameraY -= 1
+
             self.camera_y -= 1
 
</sourcecode>
 
</sourcecode>
  
 
==== Tilemap anzeigen lassen ====
 
==== Tilemap anzeigen lassen ====
Für das Tutorial sind wir jetzt mit der Tilemap fertig. Für ein kleines 2D-Spiel kann man sie gut als Grundlage verwenden und noch etwas verbessern. Etwa durch mehrere Layer, weitere Informationen der einzelne Tiles (eine eigene Tile-Klasse zum Beispiel) und natürlich Auslesen der Map-Daten aus einer Datei.
+
Für dieses Tutorial sind wir jetzt mit der Tilemap fertig. Für ein kleines 2D-Spiel kann man sie gut als Grundlage verwenden und noch etwas verbessern. Etwa durch mehrere Layer, weitere Informationen der einzelne Tiles (eine eigene <tt>Tile</tt>-Klasse zum Beispiel) und natürlich Auslesen der Map-Daten aus einer Datei.
 
Zum Schluss bauen wir die Tilemap noch in das Skript aus dem letzten Tutorial ein. Zuerst erstellen wir eine Tilemap:
 
Zum Schluss bauen wir die Tilemap noch in das Skript aus dem letzten Tutorial ein. Zuerst erstellen wir eine Tilemap:
<sourcecode lang=python line start=19>
+
 
 +
<sourcecode lang=python line start=28>
 
     map = Tilemap.Tilemap()
 
     map = Tilemap.Tilemap()
 
</sourcecode>
 
</sourcecode>
In der Event-Schrleife geben wir die gedrückten Tasten an die Map weiter:
+
 
<sourcecode lang=python line start=35>
+
In der Event-Schleife geben wir die gedrückten Tasten an die Map weiter:
                 map.handleInput(event.key)
+
 
 +
<sourcecode lang=python line start=53>
 +
                 map.handle_input(event.key)
 
</sourcecode>
 
</sourcecode>
Und direkt vorm anzeigen des Bildpuffers rendern wir die Map:
+
 
<sourcecode lang=python line start=37>
+
Und direkt vor dem Anzeigen des Bildpuffers rendern wir die Map:
 +
 
 +
<sourcecode lang=python line start=56>
 
         map.render(screen)
 
         map.render(screen)
 
</sourcecode>
 
</sourcecode>
Zeile 430: Zeile 479:
 
Und hier nochmal der gesamte Code im Überblick:
 
Und hier nochmal der gesamte Code im Überblick:
  
<tt>TileType.py</tt>:
+
<tt>Utils.py</tt>:
 
<sourcecode lang=python line>
 
<sourcecode lang=python line>
 
# -*- coding: UTF-8 -*-
 
# -*- coding: UTF-8 -*-
Zeile 436: Zeile 485:
 
import pygame
 
import pygame
  
class TileType(object):
+
# Hilfsfunktion, um ein Bild zu laden:
    def __init__(self, name, startX, startY, width, height):
+
def load_image(filename, colorkey=None):
        self.__name = name
+
    # Pygame das Bild laden lassen.
        self.__rect = pygame.rect.Rect(startX, startY, width, height)
+
 
+
    def getName(self):
+
        return self.__name
+
 
+
    def getRect(self):
+
        return self.__rect
+
</sourcecode>
+
 
+
<tt>Tileset.py</tt>:
+
<sourcecode lang=python line>
+
# -*- coding: UTF-8 -*-
+
 
+
import pygame
+
import TileType
+
 
+
def loadImage(filename, colorkey = None):
+
 
     image = pygame.image.load(filename)
 
     image = pygame.image.load(filename)
  
     if image.get_alpha() == None:
+
    # Das Pixelformat der Surface an den Bildschirm (genauer: die screen-Surface) anpassen.
 +
    # Dabei die passende Funktion verwenden, je nach dem, ob wir ein Bild mit Alpha-Kanal haben oder nicht.
 +
     if image.get_alpha() is None:
 
         image = image.convert()
 
         image = image.convert()
 
     else:
 
     else:
 
         image = image.convert_alpha()
 
         image = image.convert_alpha()
  
 +
    # Colorkey des Bildes setzen, falls nicht None.
 +
    # Bei -1 den Pixel im Bild an Position (0, 0) als Colorkey verwenden.
 
     if colorkey is not None:
 
     if colorkey is not None:
 
         if colorkey is -1:
 
         if colorkey is -1:
 
             colorkey = image.get_at((0,0))
 
             colorkey = image.get_at((0,0))
 
 
         image.set_colorkey(colorkey, pygame.RLEACCEL)
 
         image.set_colorkey(colorkey, pygame.RLEACCEL)
  
 
     return image
 
     return image
 +
</sourcecode>
  
 +
<tt>TileType.py</tt>:
 +
<sourcecode lang=python line>
 +
# -*- coding: UTF-8 -*-
  
class Tileset(object):
+
import pygame
    def __init__(self, image, colorkey, tileWidth, tileHeight):
+
        self.__image = loadImage(image, colorkey)
+
        self.__tileWidth = tileWidth
+
        self.__tileHeight = tileHeight
+
  
        self.__tileTypes = dict()
+
# Speichert die Daten eines Tile-Typs:
       
+
class TileType(object):
     def getImage(self):
+
    # Im Konstruktor speichern wir den Namen
         return self.__image
+
    # und erstellen das Rect (den Bereich) dieses Typs auf der Tileset-Grafik.
 +
     def __init__(self, name, start_x, start_y, width, height):
 +
         self.name = name
 +
        self.rect = pygame.rect.Rect(start_x, start_y, width, height)
 +
</sourcecode>
  
    def setImage(self, image, colorkey):
+
<tt>Tileset.py</tt>:
        self.__image = loadImage(image, colorkey)
+
<sourcecode lang=python line>
       
+
# -*- coding: UTF-8 -*-
    def getTileWidth(self):
+
        return self.__tileWidth
+
  
    def getTileHeight(self):
+
import pygame
        return self.__tileHeight
+
import Utils
 +
import TileType
  
     def getTileSize(self):
+
# Verwaltet die Tileset Grafik und eine Liste mit Tile-Typen.
         return (self.__tileWidth, self.__tileHeight)
+
class Tileset(object):
 +
    # Im Konstruktor laden wir die Grafik
 +
    # und erstellen ein leeres Dictionary für die Tile-Typen.
 +
     def __init__(self, image, colorkey, tile_width, tile_height):
 +
         self.image = Utils.load_image(image, colorkey)
 +
        self.tile_width = tile_width
 +
        self.tile_height = tile_height
 +
        self.tile_types = dict()
 
      
 
      
     def addTile(self, name, startX, startY):
+
    # Neuen Tile-Typ hinzufügen.
         self.__tileTypes[name] = TileType.TileType(name, startX, startY, self.__tileWidth, self.__tileHeight)
+
     def add_tile(self, name, start_x, start_y):
 +
         self.tile_types[name] = TileType.TileType(name, start_x, start_y, self.tile_width, self.tile_height)
 
          
 
          
     def getTile(self, name):
+
    # Versuchen, einen Tile-Type über seinen Name in der Liste zu finden.
 +
    # Falls der Name nicht existiert, geben wir None zurück.
 +
     def get_tile(self, name):
 
         try:
 
         try:
             return self.__tileTypes[name]
+
             return self.tile_types[name]
 
         except KeyError:
 
         except KeyError:
 
             return None
 
             return None
Zeile 509: Zeile 557:
 
# -*- coding: UTF-8 -*-
 
# -*- coding: UTF-8 -*-
  
import pygame
 
 
import random
 
import random
 +
import pygame
 
import Tileset
 
import Tileset
  
 +
# Die Tilemap Klasse verwaltet die Tile-Daten, die das Aussehen der Karte beschreiben.
 
class Tilemap(object):
 
class Tilemap(object):
     def __init__(self):      
+
     def __init__(self):
         self.__tileset = Tileset.Tileset("tileset.png", (255, 0, 255), 32, 32)
+
        # Wir erstellen ein neues Tileset.
         self.__tileset.addTile("grass", 0, 0)
+
        # Hier im Tutorial fügen wir manuell vier Tile-Typen hinzu.
         self.__tileset.addTile("mud", 32, 0)
+
         self.tileset = Tileset.Tileset("tileset.png", (255, 0, 255), 32, 32)
         self.__tileset.addTile("water", 64, 0)
+
         self.tileset.add_tile("grass", 0, 0)
         self.__tileset.addTile("block", 0, 32)
+
         self.tileset.add_tile("mud", 32, 0)
 +
         self.tileset.add_tile("water", 64, 0)
 +
         self.tileset.add_tile("block", 0, 32)
 
          
 
          
         self.__cameraX = 0
+
        # Festlegen der Startposition der Kamera. Hier (0, 0).
         self.__cameraY = 0
+
         self.camera_x = 0
           
+
         self.camera_y = 0
         self.__width = 10
+
       
         self.__height = 10
+
        # Die Größe der Maps in Tiles.
 +
         self.width = 30
 +
         self.height = 25
  
         self.__tiles = list()
+
        # Erstellen einer leeren Liste für die Tile Daten.
 +
         self.tiles = list()
 
          
 
          
         for i in range(0, self.__width):
+
        # Manuelles Befüllen der Tile-Liste:
             self.__tiles.append(list())
+
        # Jedes Feld bekommt ein zufälliges Tile zugewiesen.
             for j in range(0, self.__height):
+
         for i in range(0, self.height):
 +
             self.tiles.append(list())
 +
             for j in range(0, self.width):
 
                 x = random.randint(0, 4)
 
                 x = random.randint(0, 4)
 
                 if x == 0:
 
                 if x == 0:
                     self.__tiles[i].append("grass")
+
                     self.tiles[i].append("grass")
 
                 elif x == 1:
 
                 elif x == 1:
                     self.__tiles[i].append("water")
+
                     self.tiles[i].append("water")
 
                 elif x == 2:
 
                 elif x == 2:
                     self.__tiles[i].append("mud")
+
                     self.tiles[i].append("mud")
 
                 else:
 
                 else:
                     self.__tiles[i].append("block")  
+
                     self.tiles[i].append("block")
                   
+
 
                      
 
                      
 +
   
 +
    # Hier rendern wir den sichtbaren Teil der Karte.
 
     def render(self, screen):
 
     def render(self, screen):
         for y in range(0, int(screen.get_height() / self.__tileset.getTileHeight()) + 1):
+
        # Zeilenweise durch die Tiles durchgehen.
             ty = y + self.__cameraY
+
         for y in range(0, int(screen.get_height() / self.tileset.tile_height) + 1):
             if ty >= self.__height or ty < 0:
+
            # Die Kamera Position mit einbeziehen.
 +
             ty = y + self.camera_y
 +
             if ty >= self.height or ty < 0:
 
                 continue
 
                 continue
             col = self.__tiles[ty]
+
             # Die aktuelle Zeile zum einfacheren Zugriff speichern.
             for x in range(0, int(screen.get_width() / self.__tileset.getTileWidth()) + 1):
+
            line = self.tiles[ty]
                 tx = x + self.__cameraX
+
            # Und jetzt spaltenweise die Tiles rendern.
                 if tx >= self.__width or tx < 0:
+
             for x in range(0, int(screen.get_width() / self.tileset.tile_width) + 1):
 +
                # Auch hier müssen wir die Kamera beachten.
 +
                 tx = x + self.camera_x
 +
                 if tx >= self.width or tx < 0:
 
                     continue
 
                     continue
                 tilename = col[tx]
+
                # Wir versuchen, die Daten des Tiles zu bekommen.
                 tile = self.__tileset.getTile(tilename)
+
                 tilename = line[tx]
 +
                 tile = self.tileset.get_tile(tilename)
 +
                # Falls das nicht fehlschlägt können wir das Tile auf die screen-Surface blitten.
 
                 if tile is not None:
 
                 if tile is not None:
                     screen.blit(self.__tileset.getImage(), (x * self.__tileset.getTileWidth(), y * self.__tileset.getTileHeight()), tile.getRect())
+
                     screen.blit(self.tileset.image, (x * self.tileset.tile_width, y * self.tileset.tile_height), tile.rect)
 
                      
 
                      
 
      
 
      
     def handleInput(self, key):
+
    # Tastendrücke verarbeiten:
 +
     def handle_input(self, key):
 +
        # Pfeiltaste links oder rechts erhöht bzw. verringert die x-Position der Kamera.
 
         if key == pygame.K_LEFT:
 
         if key == pygame.K_LEFT:
             self.__cameraX += 1
+
             self.camera_x += 1
 
         if key == pygame.K_RIGHT:
 
         if key == pygame.K_RIGHT:
             self.__cameraX -= 1
+
             self.camera_x -= 1
 
+
       
 +
        # Und das gleiche nochmal für die y-Position.
 
         if key == pygame.K_UP:
 
         if key == pygame.K_UP:
             self.__cameraY += 1
+
             self.camera_y += 1
 
         if key == pygame.K_DOWN:
 
         if key == pygame.K_DOWN:
             self.__cameraY -= 1
+
             self.camera_y -= 1
 
</sourcecode>
 
</sourcecode>
  
<tt>Tutorial.py</tt>:
+
<tt>Teil_2.py</tt>:
 
<sourcecode lang=python line>
 
<sourcecode lang=python line>
 
# -*- coding: UTF-8 -*-
 
# -*- coding: UTF-8 -*-
  
 +
# Pygame Modul importieren.
 
import pygame
 
import pygame
  
if not pygame.font: print('Fehler: pygame.font Modul konnte nicht geladen werden!')
+
# Unser Tilemap Modul ebenfalls importieren.
if not pygame.mixer: print('Fehler: pygame.mixer Modul konnte nicht geladen werden!')
+
 
+
 
import Tilemap
 
import Tilemap
 +
 +
# Überprüfen, ob die optionalen Text- und Sound-Module geladen werden konnten.
 +
if not pygame.font: print('Fehler pygame.font Modul konnte nicht geladen werden!')
 +
if not pygame.mixer: print('Fehler pygame.mixer Modul konnte nicht geladen werden!')
  
 
def main():
 
def main():
 +
    # Initialisieren aller Pygame-Module und
 +
    # Fenster erstellen (wir bekommen eine Surface, die den Bildschirm repräsentiert).
 
     pygame.init()
 
     pygame.init()
 
     screen = pygame.display.set_mode((800, 600))
 
     screen = pygame.display.set_mode((800, 600))
     pygame.display.set_caption("Pygame Tutorial")
+
   
 +
    # Titel des Fensters setzen, Mauszeiger nicht verstecken und Tastendrücke wiederholt senden.
 +
     pygame.display.set_caption("Pygame-Tutorial: Tilemap")
 
     pygame.mouse.set_visible(1)
 
     pygame.mouse.set_visible(1)
 
     pygame.key.set_repeat(1, 30)
 
     pygame.key.set_repeat(1, 30)
  
 +
    # Clock Objekt erstellen, das wir benötigen, um die Framerate zu begrenzen.
 
     clock = pygame.time.Clock()
 
     clock = pygame.time.Clock()
 
      
 
      
 +
    # Wir erstellen eine Tilemap.
 
     map = Tilemap.Tilemap()
 
     map = Tilemap.Tilemap()
 
+
   
     running = 1
+
    # Die Schleife, und damit unser Spiel, läuft solange running == True.
 +
     running = True
 
     while running:
 
     while running:
 +
        # Framerate auf 30 Frames pro Sekunde beschränken.
 +
        # Pygame wartet, falls das Programm schneller läuft.
 
         clock.tick(30)
 
         clock.tick(30)
  
 +
        # screen Surface mit Schwarz (RGB = 0, 0, 0) füllen.
 
         screen.fill((0, 0, 0))
 
         screen.fill((0, 0, 0))
  
 +
        # Alle aufgelaufenen Events holen und abarbeiten.
 
         for event in pygame.event.get():
 
         for event in pygame.event.get():
 +
            # Spiel beenden, wenn wir ein QUIT-Event finden.
 
             if event.type == pygame.QUIT:
 
             if event.type == pygame.QUIT:
                 running = 0
+
                 running = False
 
+
           
 +
            # Wir interessieren uns auch für "Taste gedrückt"-Events.
 
             if event.type == pygame.KEYDOWN:
 
             if event.type == pygame.KEYDOWN:
 +
                # Wenn Escape gedrückt wird posten wir ein QUIT-Event in Pygames Event-Warteschlange.
 
                 if event.key == pygame.K_ESCAPE:
 
                 if event.key == pygame.K_ESCAPE:
                     pygame.event.post(pygame.event.Event(pygame.QUIT))  
+
                     pygame.event.post(pygame.event.Event(pygame.QUIT))
                               
+
               
                 map.handleInput(event.key)
+
                # Alle Tastendrücke auch der Tilemap mitteilen.
 
+
                 map.handle_input(event.key)
 +
       
 +
        # Die Tilemap auf die screen-Surface rendern.
 
         map.render(screen)
 
         map.render(screen)
 
          
 
          
 +
        # Inhalt von screen anzeigen
 
         pygame.display.flip()
 
         pygame.display.flip()
  
  
 +
# Überprüfen, ob dieses Modul als Programm läuft und nicht in einem anderen Modul importiert wird.
 
if __name__ == '__main__':
 
if __name__ == '__main__':
 +
    # Unsere Main-Funktion aufrufen.
 
     main()
 
     main()
 
</sourcecode>
 
</sourcecode>
Zeile 622: Zeile 711:
 
=== Der Plan ===
 
=== Der Plan ===
 
[[Datei:Pygame-Tutorial_Animation.png|thumb|right|Screenshot der animierten Spielfigur.]]
 
[[Datei:Pygame-Tutorial_Animation.png|thumb|right|Screenshot der animierten Spielfigur.]]
Nachdem wir gerade eine einfache Tilemap programmiert haben, wollen wir jetzt eine Spielfigur über unsere Karte laufen lassen. Dazu erstellen wir eine <tt>Player</tt>-Klasse. In umserem einfachen Tutorial soll diese Klasse folgende Dinge können: ein Bild an der Position der Spielfigur anzeigen, die Position verändern, wenn der Spieler die Pfelltasten drückt, und die Spielfigur animieren, um eine Gehbewegung dazustellen. Die ersten beiden Punkte können wir bereits programmieren. Schauen wir uns also erstmal an, wie das mit der Animation funktioniert.
+
Nachdem wir nun bereits eine einfache Tilemap programmiert haben, wollen wir jetzt eine Spielfigur über unsere Karte laufen lassen. Dazu erstellen wir eine <tt>Player</tt>-Klasse. In umserem einfachen Tutorial soll diese Klasse folgende Dinge können: ein Bild an der Position der Spielfigur anzeigen, die Position verändern, wenn der Spieler die Pfeiltasten drückt, und die Spielfigur animieren, um eine Gehbewegung dazustellen. Die ersten beiden Punkte können wir bereits programmieren. Schauen wir uns also erstmal an, wie das mit der Animation funktioniert.
  
=== Die Animation ===
+
=== Die Animation-Klasse ===
Die Gehbewegung der Spielfigur entsteht, indem wir in schneller Folge die Grafiken der Spielfigur austauschen. In diesem Tutorial speichern wir alle Einzelbilder, oder Frames einer Animation, in einer Bilddatei. Außerdem müssen die Frames in einer Reihe liegen und natürlich alle gleich groß sein.
+
Die Gehbewegung der Spielfigur entsteht, indem wir in schneller Folge die Grafiken der Spielfigur austauschen. In diesem Tutorial speichern wir alle Einzelbilder (''Frames'') einer Animation, in einer einzigen Bilddatei. Außerdem müssen die Frames in einer Reihe nebeneinander liegen und alle gleich groß sein.
  
 
Wir beginnen damit, eine Klasse für eine Animation zu programmieren.
 
Wir beginnen damit, eine Klasse für eine Animation zu programmieren.
 +
 
<sourcecode lang=python line start=5>
 
<sourcecode lang=python line start=5>
 +
# Die Klasse kümmert sich um eine einfache Animation:
 
class Animation(object):
 
class Animation(object):
     def __init__(self, image, startX, startY, num, width, height, duration):
+
     def __init__(self, image, start_x, start_y, num, width, height, duration):
         self.__image = image
+
         # Die Surface speichern,
         self.__startX = startX
+
         # alle Einzelbilder müssen in einer Reihe liegen.
         self.__startY = startY
+
         self.image = image
        self.__num = num
+
        self.__width = width
+
        self.__height = height
+
        self.__duration = duration
+
 
          
 
          
         self.__time = 0
+
        # Dazu müssen wir wissen, an welcher Position die Frames beginnen,
         self.__current = 0
+
        # wie viele Frames die Animation hat,
 +
        # sowie die Breite und Höhe der Animation kennen.
 +
        self.start_x = start_x
 +
        self.start_y = start_y
 +
        self.num = num
 +
        self.width = width
 +
        self.height = height
 +
       
 +
        # Und natürlich auch, nach welchem Zeitraum wir das nächsten Frame anzeigen sollen.
 +
        self.duration = duration
 +
       
 +
        # Die aktuelle Zeit und das aktuellen Frame speichern wir ebenfalls.
 +
         self.time = 0
 +
         self.current = 0
 
</sourcecode>
 
</sourcecode>
Im Konstruktor passiert nicht viel. Um die Animation darstellen zu können benötigen wir ein paar Daten: Natürlich das Bild, wir erwarten hier aber keinen Dateinamen sondern direkt eine Surface mit den Bilddaten. Das hat den Vorteil, dass wir in einem Bild mehrere Animationen unterbringen können und nicht unnötigerweise das Bild für jede Animation komplett geladen wird. Aus diesem Grund müssen wir auch wissen, an welcher Position im Bild die Animation beginnt: <tt>startX</tt> und <tt>startY</tt>. Dazu brauchen wir die Abmessungen der Animation: <tt>width</tt>, <tt>height</tt> und die Anzahl der Einzelbilder: <tt>num</tt>. Jetzt fehlt noch die Zeit, die ein Frame angezeigt wird bevor zum nächsten gewechselt wird: <tt>duration</tt>.
+
 
 +
Im Konstruktor passiert nicht viel. Um die Animation darstellen zu können, benötigen wir ein paar Daten: Natürlich das Bild, wir erwarten hier aber keinen Dateinamen sondern, direkt eine Surface mit den Bilddaten. Das hat den Vorteil, dass wir in einem Bild mehrere Animationen unterbringen können und nicht unnötigerweise das Bild für jede Animation komplett geladen wird. Aus diesem Grund müssen wir auch wissen, an welcher Position im Bild die Animation beginnt: <tt>start_x</tt> und <tt>start_y</tt>. Dazu brauchen wir die Abmessungen der Animation (<tt>width</tt>, <tt>height</tt>) und die Anzahl der Einzelbilder (<tt>num</tt>). Jetzt fehlt noch die Zeitspanne, für die ein Frame angezeigt, wird bevor zum nächsten gewechselt wird: <tt>duration</tt>.
  
 
Das Rendern der Animation erledigen wir in einer Zeile:
 
Das Rendern der Animation erledigen wir in einer Zeile:
<sourcecode lang=python line start=29>
+
 
def render(self, screen, pos):
+
<sourcecode lang=python line start=44>
         screen.blit(self.__image, pos, pygame.Rect(self.__startX + (self.__width * self.__current), self.__startY, self.__width, self.__height))
+
    # Das aktuelle Frame an einer bestimmten Position rendern:
 +
    def render(self, screen, pos):
 +
        # Welchen Bereich aus der Grafik müssen wir anzeigen?
 +
        # Die x-Position können wir aus der Breite und der Start-Position berechnen,
 +
        # die restlichen Werte kennen wir bereits.
 +
         screen.blit(self.image, pos, pygame.Rect(self.start_x + (self.width * self.current), self.start_y, self.width, self.height))
 
</sourcecode>
 
</sourcecode>
Wir benötigen hier wieder eine Surface auf die wir zeichnen sollen, und dazu eine Positionsangabe. Diese beiten Parameter geben wir so direkt an die <tt>blit</tt>-Methode weiter. Fehlt nur noch die Information, welcher Bereich des Bildes genau dargestellt werden soll. Dazu basteln wir ein Rechteck (<tt>pygame.Rect</tt>) mit den folgenden Daten: Der Beginn unserer Animation ist in <tt>__startX</tt> und <tt>__startY</tt> gespeichert. Da wir festgelegt haben, dass sich unsere Frames alle in einer Reihe befinden müssen, addieren wir die Breite des Einzelbildes multipliziert mit der Nummer des aktuellen Einzelbild zur X-Position dazu. Das bringt uns exakt zu dem Einzelbild welches wir anzeigen wollen. Die Größe der Animation kennen wir bereits.
 
  
Jetzt müssen wir die Animation nur noch aktualisieren, momentan würde immer nur das gleiche Bild (das 0-te) angezeigt.
+
Wir benötigen hier wieder eine Surface, auf die wir zeichnen sollen, und dazu eine Positionsangabe. Diese beiden Parameter geben wir so direkt an die <tt>blit</tt>-Methode weiter. Fehlt nur noch die Information, welcher Bereich des Bilds genau dargestellt werden soll. Dazu basteln wir ein Rechteck (<tt>pygame.Rect</tt>) mit den folgenden Daten: Der Beginn unserer Animation ist in <tt>start_x</tt> und <tt>start_y</tt> gespeichert. Da wir festgelegt haben, dass sich unsere Frames alle in einer Reihe befinden müssen, addieren wir die Breite des Einzelbildes multipliziert mit der Nummer des aktuellen Einzelbild zur x-Position dazu. Das bringt uns exakt zu dem Einzelbild, das wir anzeigen wollen. Die Größe der Animation kennen wir bereits.
<sourcecode lang=python line start=19>
+
 
def update(self, time = 1):
+
Jetzt müssen wir die Animation nur noch aktualisieren, denn momentan würde immer nur das gleiche Bild (das erste) angezeigt.
         self.__time += time
+
 
 +
<sourcecode lang=python line start=29>
 +
    # Die update-Methode rufen wir einmal pro Frame auf:
 +
    def update(self, time=1):
 +
        # Die vergangene Zeit addieren
 +
         self.time += time
 
          
 
          
         if self.__time > self.__duration:
+
        # Falls wir den Anzeige-Zeitraum überschreiten, ...
             self.__time = 0
+
         if self.time > self.duration:
             self.__current += 1
+
            # ... setzten wir die Zeit zurück und gehen zum nächsten Frame.
             if self.__current >= self.__num:
+
             self.time = 0
                 self.__current = 0
+
             self.current += 1
 +
            # Sicherstellen, dass das aktuelle Frame auch verfügbar ist.
 +
             if self.current >= self.num:
 +
                 self.current = 0
 
</sourcecode>
 
</sourcecode>
Ein Einzelbild der Animation soll immer nur eine bestimmte Zeit lang angezeigt werden. Der <tt>update</tt>-Methode übergeben wir einfach die Zeit, die seit dem letzten Aufruf der Methode vergangen sein soll. Hier in dem Tutorial machen wir das auf Frame Basis, deshalb erhöhen wir standardmäßig die Zeit um 1 und rufen die <tt>update</tt>-Methode später jeden Frame genau einmal auf. Ab Zeile 22 überprüfen wir, ob genügend viel Zeit vergangen ist und wir ein neuen Frame anzeigen müssen. Wenn die Zeit größer als die Anzeigedauer für ein Einzelbild ist setzen wir zuerst die Zeit zurück auf 0 und erhöhen die Nummer des aktuellen Einzelbildes. Im Tutorial lassen wir unsere Animation einfach am Anfang weiterspielen wenn sie fertig ist (loopen), deshalb sorgen wir noch dafür dass <tt>__current</tt> im erlaubten Wertebereich bleibt.
 
  
Und damit ist unsere Animations Klasse schon fertig. Den Rest erledigt die Player Klasse.
+
Ein Einzelbild der Animation soll immer nur eine bestimmte Zeit lang angezeigt werden. Der <tt>update</tt>-Methode übergeben wir einfach die Zeit, die seit dem letzten Aufruf der Methode vergangen ist. Hier in dem Tutorial machen wir das auf Frame-Basis, deshalb erhöhen wir standardmäßig die Zeit um 1 und rufen die <tt>update</tt>-Methode später in jedem Frame genau einmal auf. Ab Zeile 22 überprüfen wir, ob genügend viel Zeit vergangen ist und wir ein neues Frame anzeigen müssen. Wenn die Zeit größer als die Anzeigedauer für ein Einzelbild ist, setzen wir zuerst die Zeit zurück auf 0 und erhöhen die Nummer des aktuellen Einzelbildes. Im Tutorial lassen wir unsere Animation einfach am Anfang weiterspielen wenn sie fertig ist (Looping), deshalb sorgen wir noch dafür, dass <tt>current</tt> im erlaubten Wertebereich bleibt.
 +
 
 +
Und damit ist unsere Animations-Klasse schon fertig. Den Rest erledigt die <tt>Player</tt>-Klasse.
 +
 
 +
=== Die Player-Klasse ===
 +
Unsere <tt>Player</tt>-Klasse verwendet jetzt die soeben programmierte Animationsklasse, um zwei Animationen darzustellen: Laufen nach links und rechts. Wir machen es uns wieder einfach und erstellen dafür einfach zwei <tt>Animation</tt>-Objekte. Nicht vergessen: Der Konstuktor der <tt>Animation</tt>-Klasse erwartet keinen Bildnamen, sondern das geladene Bild.
  
=== Die Player Klasse ===
 
Unsere Player Klasse verwendet jetzt die soeben programierte Animations Klasse um zwei Animationen darzustellen: Laufen nach links und rechts. Wir machen es uns wieder einfach und erstellen dafür einfach zwei <tt>Animation</tt> Objekte. Nicht vergessen: Der Konstuktor der <tt>Animation</tt> Klasse erwartet keinen Bildname sondern das geladene Bild.
 
 
<sourcecode lang=python line start=7>
 
<sourcecode lang=python line start=7>
 +
# Die Player Klasse verwendet zwei Animationen, um eine steuerbare Spielfigur dazustellen.
 
class Player(object):
 
class Player(object):
 
     def __init__(self):
 
     def __init__(self):
         self.__anim_image_right = loadImage("tileset.png", (255, 0, 255))
+
        # Bild laden und erste Animation erstellen:
         self.__anim_right = Animation.Animation(self.__anim_image_right, 32, 32, 2, 32, 64, 15)
+
         self.anim_image_right = Utils.loadImage("tileset.png", (255, 0, 255))
 +
         self.anim_right = Animation.Animation(self.anim_image_right, 32, 32, 2, 32, 64, 15)
 
</sourcecode>
 
</sourcecode>
Die <tt>loadImage</tt> Funktion kennen wir noch von früher, wir verwenden sogar das selbe Bild wie für die Tilemap (und verdrängen kurz, dass das Bild natürlich jetzt unnötigerweise zweimal geladen wird). In Zeile 10 erstellen wir dann die Animation für das ''nach rechts gehen'': Unser soeben geladenes Bild, Anfangs X-Position und Y-Position ist jeweils 32, wir haben 2 Einzelbilder die wir anzeigen möchten, die Breite eines Frames ist 32, die Höhe 64 und wir wollen eine Dauer von 15 (und in unserem Fall heißt dass 15 Frames).
 
  
Kommen wir zur ''links gehen Animation''. Normalerweise müssten wir dafür neue Grafiken erstellen, hier würde es sogar reichen, die Animation einfach zu spiegeln. Aber geht es nicht noch einfacher? Natürlich. Im Modul <tt>pygame.transform</tt> (http://www.pygame.org/docs/ref/transform.html) gibt es die Methode <tt>flip</tt>:
+
Die <tt>loadImage</tt>-Funktion kennen wir noch von früher, wir verwenden sogar das selbe Bild wie für die Tilemap (und verdrängen kurz, dass das Bild natürlich jetzt unnötigerweise zweimal geladen wird). In Zeile 10 erstellen wir dann die Animation für das ''nach rechts gehen'': Unser soeben geladenes Bild, Anfangsposition ist (32, 32), wir haben 2 Einzelbilder, die wir anzeigen möchten, die Breite eines Frames ist 32, die Höhe 64, und wir wollen eine Dauer von 15 (in unserem Fall heißt das 15 Frames).
 +
 
 +
Kommen wir zur "links gehen"-Animation. Normalerweise müssten wir dafür neue Grafiken erstellen. Hier würde es sogar reichen, die Animation einfach zu spiegeln. Aber geht es nicht noch einfacher? Natürlich. Im Modul <tt>pygame.transform</tt> ([http://www.pygame.org/docs/ref/transform.html Dokumentation]) gibt es die Methode <tt>flip</tt>:
 +
 
 
<sourcecode lang=python>
 
<sourcecode lang=python>
 
pygame.transform.flip(Surface, xbool, ybool): return Surface
 
pygame.transform.flip(Surface, xbool, ybool): return Surface
 
</sourcecode>
 
</sourcecode>
Damit können wir unser Bild einfach horizontal (das brauchen wir) oder vertikal spiegeln. Dabei wird eine neue Surface erstellt die wir für unsere zweite Animation verwenden können.
+
 
<sourcecode lang=python line start=12>
+
Damit können wir unser Bild einfach horizontal (das brauchen wir) oder vertikal spiegeln. Dabei wird eine neue Surface erstellt, die wir für unsere zweite Animation verwenden können.
         self.__anim_image_left = pygame.transform.flip(self.__anim_image_right, True, False)
+
 
         self.__anim_left = Animation.Animation(self.__anim_image_left, 32, 32, 2, 32, 64, 15)
+
<sourcecode lang=python line start=14>
 +
        # Die Grafik spiegeln und in einer neuen Surface speichern,
 +
        # dann können wir die linke Animation erstellen.
 +
         self.anim_image_left = pygame.transform.flip(self.anim_image_right, True, False)
 +
         self.anim_left = Animation.Animation(self.anim_image_left, 32, 32, 2, 32, 64, 15)
 
</sourcecode>
 
</sourcecode>
Und diese zweite Animation erstellen wir nach dem selben Muster wie zuvor. Wir können uns außerdem für später merken: Im <tt>transform</tt>-Modul gibt es noch weitere Funktionen wie skalieren oder rotieren von Surfaces. Der Rest des Konstruktors speichert nur noch die Position des Players und die Blickrichtung und ob wir gerade am laufen sind:
+
 
<sourcecode lang=python line start=15>
+
Und diese zweite Animation erstellen wir nach dem selben Muster wie zuvor. Wir können uns außerdem für später merken: Im <tt>transform</tt>-Modul gibt es noch weitere Funktionen zum Skalieren oder Rotieren von Surfaces. Der Rest des Konstruktors speichert nur noch die Position des Spielers, die Blickrichtung und ob wir gerade laufen oder nicht:
         self.__posX = 10*32
+
 
         self.__posY = 13*32         
+
<sourcecode lang=python line start=19>
         self.__dir = 0
+
        # Startposition des Players festlegen und
         self.__walking = False
+
        # merken, in welche Richtung wir schauen, und ob wir überhaupt laufen.
 +
         self.pos_x = 10*32
 +
         self.pos_y = 13*32         
 +
         self.dir = 0
 +
         self.walking = False
 
</sourcecode>
 
</sourcecode>
Diese Werte kann der Spieler später über die Pfeiltasten beeinflussen. Das wird in der <tt>handleInput</tt> Methode erledigt:
+
 
<sourcecode lang=python line start=34>
+
Diese Werte kann der Spieler später über die Pfeiltasten beeinflussen. Das wird in der <tt>handle_input</tt>-Methode erledigt:
     def handleInput(self, key):
+
 
 +
<sourcecode lang=python line start=46>
 +
     def handle_input(self, key):
 +
        # Linke Pfeiltaste wird gedrückt:
 
         if key == pygame.K_LEFT:
 
         if key == pygame.K_LEFT:
             self.__posX -= 1
+
            # x-Position der Spielfigur anpassen,
             self.__dir = -1
+
            # die Blickrichtung festlegen
             self.__walking = True
+
            # und den Laufen-Zustand einschalten.
 +
             self.pos_x -= 1
 +
             self.dir = -1
 +
             self.walking = True
 +
       
 +
        # Und nochmal für die rechte Pfeiltaste.
 
         if key == pygame.K_RIGHT:
 
         if key == pygame.K_RIGHT:
             self.__posX += 1
+
             self.pos_x += 1
             self.__dir = 1
+
             self.dir = 1
             self.__walking = True
+
             self.walking = True
 
</sourcecode>
 
</sourcecode>
Das ist sehr ähnlich der <tt>handleInput</tt> Methode unserer <tt>Tilemap</tt> Klasse. Je nachdem welche Pfeiltaste gedrückt wird erhöhen bzw. verringern wir die X-Position des Players (damit kann er nach rechts und links laufen) und passen die Blickrichtung an: -1 für links und +1 für rechts. Außerdem sind wir in beiden Fällen am laufen, setzten <tt>__walking</tt> also auf <tt>True</tt>. Diese Information benötigen wir zum rendern:
+
 
<sourcecode lang=python line start=21>
+
Das ist sehr ähnlich zur <tt>handle_input</tt>-Methode unserer <tt>Tilemap</tt>-Klasse. Je nach dem, welche Pfeiltaste gedrückt wird, erhöhen bzw. verringern wir die x-Position des Spielers (damit kann er nach rechts und links laufen) und passen die Blickrichtung an: -1 für links und +1 für rechts. Außerdem sind wir in beiden Fällen am laufen, setzen <tt>walking</tt> also auf <tt>True</tt>. Diese Information benötigen wir zum Rendern:
     def render(self, screen):      
+
 
         if self.__dir == -1:  
+
<sourcecode lang=python line start=27>
             if self.__walking:
+
     def render(self, screen):
                 self.__anim_left.update()
+
        # Die Blickrichtung ist links:
             self.__anim_left.render(screen, (self.__posX, self.__posY))   
+
         if self.dir == -1:
         else:    
+
            # Wenn der Spieler die linke oder rechte Pfeiltaste gedrückt hat sind wir am laufen,
             if self.__walking:
+
             if self.walking:              
                 self.__anim_right.update()
+
                # nur dann die Animation updaten.
             self.__anim_right.render(screen, (self.__posX, self.__posY))
+
                 self.anim_left.update()
           
+
            # Blickrichtung links rendern.
         self.__walking = False;
+
             self.anim_left.render(screen, (self.pos_x, self.pos_y))   
 +
         else:
 +
            # Und das gleiche nochmal für rechts:
 +
             if self.walking:
 +
                 self.anim_right.update()
 +
             self.anim_right.render(screen, (self.pos_x, self.pos_y))
 +
       
 +
        # De Laufen-Zustand zurücksetzen, im nächsten Frame bleiben wir stehen.
 +
         self.walking = False
 
</sourcecode>
 
</sourcecode>
Die <tt>render</tt> Methode sieht komplizierter aus als sie es ist. Wir haben drei mögliche Zustände, in denen sich der Player befinden kann: Stehen, links gehen oder rechts gehen. Dafür haben wir vorher die Blickrichtung gespeichert. Schauen wir zuerst mal, was passiert wenn der Spieler weder die rechte noch die linke Pfeiltaste gedrückt hat: <tt>__dir </tt> ist 0 (das haben wir im Konstruktor so festgelegt), wir kommen also zu Zeile 26. Der Einfachheit halber lassen wir in diesem Fall den Player einfach immer nach rechts schauen, später wird die Blickrichtung erhalten bleiben. Da <tt>__walking</tt> <tt>False</tt> ist aktualisieren wir die Animation nicht, das Einzelbild verändert sich also nicht. Am Ende der Methode setzten wir <tt>__walking</tt> wieder auf <tt>False</tt> damit wir im nächsten Frame stehen (natürlich nur falls, der Spieler keine Pfeiltaste mehr drückt). Die Blickrichtung setzen wir nicht zurück, damit wissen wir im nächsten Frame noch welche Animation wir anzeigen sollen, auch wenn der Spieler keine Pfeiltaste gedrückt hat. Der Zustand für rechts gehen (mit <tt>__dir</tt> ist +1) ist exakt der gleiche den wir eben angesehen haben und für die Variante nach links gehen (<tt>__dir</tt> ist -1) aktualisieren und zeichnen wir anstatt der rechten Animation eben die linke.
 
  
Jetzt fehlt nur noch die Einbindung der Player Klasse in unser Beispiel Spiel.
+
Die <tt>render</tt>-Methode sieht komplizierter aus als sie es ist. Wir haben drei mögliche Zustände, in denen sich der Spieler befinden kann: Stehen, links gehen und rechts gehen. Dafür haben wir vorher die Blickrichtung gespeichert. Schauen wir zuerst mal, was passiert, wenn der Spieler weder die rechte noch die linke Pfeiltaste gedrückt hat: <tt>dir </tt> ist 0 (das haben wir im Konstruktor so festgelegt), wir kommen also zu Zeile 26. Der Einfachheit halber lassen wir in diesem Fall den Spieler einfach immer nach rechts schauen, später wird die Blickrichtung erhalten bleiben. Da <tt>walking = False</tt> ist, aktualisieren wir die Animation nicht, das Einzelbild verändert sich also nicht. Am Ende der Methode setzen wir <tt>walking</tt> wieder auf <tt>False</tt>, damit wir im nächsten Frame stehen (natürlich nur falls, der Spieler keine Pfeiltaste mehr drückt). Die Blickrichtung setzen wir nicht zurück, damit wissen wir im nächsten Frame noch, welche Animation wir anzeigen sollen, auch wenn der Spieler keine Pfeiltaste gedrückt hat. Der Zustand für "rechts gehen" (mit <tt>dir</tt> ist 1) ist exakt der gleiche wie der, den wir eben angesehen haben, und für die Variante "links gehen" (<tt>dir</tt> ist -1) aktualisieren und zeichnen wir anstatt der rechten Animation eben die linke.
 +
 
 +
Jetzt fehlt nur noch die Einbindung der <tt>Player</tt>-Klasse in unser Beispielspiel.
  
 
=== Zusammenbasteln ===
 
=== Zusammenbasteln ===
 
Wann erstellen wir ein <tt>Player</tt>-Objekt? Wenn das Level geladen wird. Hier im Tutorial passiert das alles im Konstruktor der <tt>Tilemap</tt>-Klasse. Zuerst erweitern wir das Tileset um zwei neue Tiles:
 
Wann erstellen wir ein <tt>Player</tt>-Objekt? Wenn das Level geladen wird. Hier im Tutorial passiert das alles im Konstruktor der <tt>Tilemap</tt>-Klasse. Zuerst erweitern wir das Tileset um zwei neue Tiles:
<sourcecode lang=python line start=16>
+
 
         self.__tileset.addTile("grass-mud", 0, 64)
+
<sourcecode lang=python line start=15>
         self.__tileset.addTile("empty", 0, 96)
+
         self.tileset.add_tile("grass-mud", 0, 64)
 +
         self.tileset.add_tile("empty", 0, 96)
 
</sourcecode>
 
</sourcecode>
 +
 
Und dann passen wir die Generierung der Tilemap etwas an:
 
Und dann passen wir die Generierung der Tilemap etwas an:
<sourcecode lang=python line start=27>
+
 
         for i in range(0, self.__width):
+
<sourcecode lang=python line start=30>
             self.__tiles.append(list())
+
         for i in range(0, self.height):
             for j in range(0, self.__height):
+
             self.tiles.append(list())
 +
             for j in range(0, self.width):
 
                 if i == 14:
 
                 if i == 14:
                     self.__tiles[i].append("grass")  
+
                     self.tiles[i].append("grass")  
 
                 elif i == 15:
 
                 elif i == 15:
                     self.__tiles[i].append("grass-mud")                  
+
                     self.tiles[i].append("grass-mud")
 
                 elif i > 15:
 
                 elif i > 15:
                     self.__tiles[i].append("mud")
+
                     self.tiles[i].append("mud")
 
                 else:
 
                 else:
                     self.__tiles[i].append("empty")
+
                     self.tiles[i].append("empty")
 
</sourcecode>
 
</sourcecode>
Um am Ende des Konstuktors endlich einen Player erstellen zu können:
+
 
<sourcecode lang=python line start=39>
+
Um am Ende des Konstuktors endlich einen Spieler erstellen zu können:
         self.__player = Player.Player()
+
 
 +
<sourcecode lang=python line start=43>
 +
         self.player = Player.Player()
 
</sourcecode>
 
</sourcecode>
In der <tt>render</tt>-Methode rendern wir den Player:
+
 
<sourcecode lang=python line start=56>
+
In der <tt>render</tt>-Methode rendern wir den Spieler:
         self.__player.render(screen)
+
 
 +
<sourcecode lang=python line start=70>
 +
         self.player.render(screen)
 
</sourcecode>
 
</sourcecode>
Und in der <tt>handleInput</tt>-Methode reichen wir die Tastendrücke durch:
+
 
<sourcecode lang=python line start=60>
+
Und in der <tt>handle_input</tt>-Methode reichen wir die Tastendrücke durch:
         self.__player.handleInput(key)
+
 
 +
<sourcecode lang=python line start=75>
 +
         self.player.handle_input(key)
 
</sourcecode>
 
</sourcecode>
Und fertig, mit den Pfeiltasten können wir unseren animierten Player jetzt nach rechts und links über die Tilemap bewegen. Wunderbar!
+
 
 +
Und fertig. Mit den Pfeiltasten können wir unseren animierten Player jetzt nach rechts und links über die Tilemap bewegen.
  
 
Hier nochmal der neue Code im Überblick:
 
Hier nochmal der neue Code im Überblick:
 +
 +
<tt>TileType.py</tt>, <tt>Tileset.py</tt> und <tt>Utils.py</tt> haben sich nicht verändert.
  
 
<tt>Animation.py</tt>:
 
<tt>Animation.py</tt>:
Zeile 764: Zeile 920:
 
import pygame
 
import pygame
  
 +
# Die Klasse kümmert sich um eine einfache Animation:
 
class Animation(object):
 
class Animation(object):
     def __init__(self, image, startX, startY, num, width, height, duration):
+
     def __init__(self, image, start_x, start_y, num, width, height, duration):
         self.__image = image
+
         # Die Surface speichern,
         self.__startX = startX
+
         # alle Einzelbilder müssen in einer Reihe liegen.
         self.__startY = startY
+
         self.image = image
        self.__num = num
+
        self.__width = width
+
        self.__height = height
+
        self.__duration = duration
+
 
          
 
          
         self.__time = 0
+
        # Dazu müssen wir wissen, an welcher Position die Frames beginnen,
         self.__current = 0
+
        # wie viele Frames die Animation hat,
 +
        # sowie die Breite und Höhe der Animation kennen.
 +
        self.start_x = start_x
 +
        self.start_y = start_y
 +
        self.num = num
 +
        self.width = width
 +
        self.height = height
 +
       
 +
        # Und natürlich auch, nach welchem Zeitraum wir das nächsten Frame anzeigen sollen.
 +
        self.duration = duration
 +
       
 +
        # Die aktuelle Zeit und das aktuellen Frame speichern wir ebenfalls.
 +
         self.time = 0
 +
         self.current = 0
 
          
 
          
 
      
 
      
     def update(self, time = 1):
+
    # Die update-Methode rufen wir einmal pro Frame auf:
         self.__time += time
+
     def update(self, time=1):
 +
        # Die vergangene Zeit addieren
 +
         self.time += time
 
          
 
          
         if self.__time > self.__duration:
+
        # Falls wir den Anzeige-Zeitraum überschreiten, ...
             self.__time = 0
+
         if self.time > self.duration:
             self.__current += 1
+
            # ... setzten wir die Zeit zurück und gehen zum nächsten Frame.
             if self.__current >= self.__num:
+
             self.time = 0
                 self.__current = 0
+
             self.current += 1
 +
            # Sicherstellen, dass das aktuelle Frame auch verfügbar ist.
 +
             if self.current >= self.num:
 +
                 self.current = 0
 
                  
 
                  
       
+
   
 +
    # Das aktuelle Frame an einer bestimmten Position rendern:
 
     def render(self, screen, pos):
 
     def render(self, screen, pos):
         screen.blit(self.__image, pos, pygame.Rect(self.__startX + (self.__width * self.__current), self.__startY, self.__width, self.__height))
+
        # Welchen Bereich aus der Grafik müssen wir anzeigen?
 +
        # Die x-Position können wir aus der Breite und der Start-Position berechnen,
 +
        # die restlichen Werte kennen wir bereits.
 +
         screen.blit(self.image, pos, pygame.Rect(self.start_x + (self.width * self.current), self.start_y, self.width, self.height))
 
</sourcecode>
 
</sourcecode>
  
Zeile 797: Zeile 972:
  
 
import pygame
 
import pygame
 +
import Utils
 
import Animation
 
import Animation
import Tileset              # für loadImage
 
  
 +
# Die Player Klasse verwendet zwei Animationen, um eine steuerbare Spielfigur dazustellen.
 
class Player(object):
 
class Player(object):
 
     def __init__(self):
 
     def __init__(self):
         self.__anim_image_right = Tileset.loadImage("tileset.png", (255, 0, 255))
+
        # Bild laden und erste Animation erstellen:
         self.__anim_right = Animation.Animation(self.__anim_image_right, 32, 32, 2, 32, 64, 15)   
+
         self.anim_image_right = Utils.load_image("tileset.png", (255, 0, 255))
 +
         self.anim_right = Animation.Animation(self.anim_image_right, 32, 32, 2, 32, 64, 15)   
 
          
 
          
         self.__anim_image_left = pygame.transform.flip(self.__anim_image_right, True, False)
+
        # Die Grafik spiegeln und in einer neuen Surface speichern,
         self.__anim_left = Animation.Animation(self.__anim_image_left, 32, 32, 2, 32, 64, 15)
+
        # dann können wir die linke Animation erstellen.
 +
         self.anim_image_left = pygame.transform.flip(self.anim_image_right, True, False)
 +
         self.anim_left = Animation.Animation(self.anim_image_left, 32, 32, 2, 32, 64, 15)
 
          
 
          
         self.__posX = 10*32
+
        # Start-Position des Players festlegen und
         self.__posY = 13*32         
+
        # merken in welche Richtung wir schauen und ob wir überhaupt laufen.
         self.__dir = 0
+
         self.pos_x = 10*32
         self.__walking = False
+
         self.pos_y = 13*32         
 +
         self.dir = 0
 +
         self.walking = False
 
          
 
          
 
          
 
          
     def render(self, screen):        
+
     def render(self, screen):
         if self.__dir == -1:  
+
        # Die Blickrichtung ist links:
             if self.__walking:
+
         if self.dir == -1:
                 self.__anim_left.update()
+
            # Wenn der Spieler die linke oder rechte Pfeiltaste gedrückt hat sind wir am laufen,
             self.__anim_left.render(screen, (self.__posX, self.__posY))   
+
             if self.walking:              
         else:    
+
                # nur dann die Animation updaten.
             if self.__walking:
+
                 self.anim_left.update()
                 self.__anim_right.update()
+
            # Blickrichtung links rendern.
             self.__anim_right.render(screen, (self.__posX, self.__posY))
+
             self.anim_left.render(screen, (self.pos_x, self.pos_y))   
           
+
         else:
        self.__walking = False
+
            # Und das gleiche nochmal für rechts:
 +
             if self.walking:
 +
                 self.anim_right.update()
 +
             self.anim_right.render(screen, (self.pos_x, self.pos_y))
 
          
 
          
 +
        # De Laufen-Zustand zurücksetzen, im nächsten Frame bleiben wir stehen.
 +
        self.walking = False
 
          
 
          
     def handleInput(self, key):
+
   
 +
     def handle_input(self, key):
 +
        # Linke Pfeiltaste wird gedrückt:
 
         if key == pygame.K_LEFT:
 
         if key == pygame.K_LEFT:
             self.__posX -= 1
+
            # x-Position der Spielfigur anpassen,
             self.__dir = -1
+
            # die Blickrichtung festlegen
             self.__walking = True
+
            # und den Laufen-Zustand einschalten.
 +
             self.pos_x -= 1
 +
             self.dir = -1
 +
             self.walking = True
 +
       
 +
        # Und nochmal für die rechte Pfeiltaste.
 
         if key == pygame.K_RIGHT:
 
         if key == pygame.K_RIGHT:
             self.__posX += 1
+
             self.pos_x += 1
             self.__dir = 1
+
             self.dir = 1
             self.__walking = True
+
             self.walking = True
 
</sourcecode>
 
</sourcecode>
  
Zeile 843: Zeile 1.036:
  
 
import pygame
 
import pygame
import random
 
 
import Tileset
 
import Tileset
 
import Player
 
import Player
  
 +
# Die Tilemap Klasse verwaltet die Tile-Daten, die das Aussehen der Karte beschreiben.
 
class Tilemap(object):
 
class Tilemap(object):
     def __init__(self):      
+
     def __init__(self):
         self.__tileset = Tileset.Tileset("tileset.png", (255, 0, 255), 32, 32)
+
        # Wir erstellen ein neues Tileset.
         self.__tileset.addTile("grass", 0, 0)
+
        # Hier im Tutorial fügen wir manuell vier Tile-Typen hinzu.
         self.__tileset.addTile("mud", 32, 0)
+
         self.tileset = Tileset.Tileset("tileset.png", (255, 0, 255), 32, 32)
         self.__tileset.addTile("water", 64, 0)
+
         self.tileset.add_tile("grass", 0, 0)
         self.__tileset.addTile("block", 0, 32)
+
         self.tileset.add_tile("mud", 32, 0)      
 +
         self.tileset.add_tile("grass-mud", 0, 64)
 +
         self.tileset.add_tile("empty", 0, 96)
 
          
 
          
         self.__tileset.addTile("grass-mud", 0, 64)
+
         # Festlegen der Startposition der Kamera. Hier (0, 0).
         self.__tileset.addTile("empty", 0, 96)
+
         self.camera_x = 0
 +
        self.camera_y = 0
 
          
 
          
         self.__cameraX = 0
+
         # Die Größe der Maps in Tiles.
        self.__cameraY = 0
+
         self.width = 30
           
+
         self.height = 25
         self.__width = 25
+
         self.__height = 25
+
  
         self.__tiles = list()
+
        # Erstellen einer leeren Liste für die Tile Daten.
          
+
         self.tiles = list()
         for i in range(0, self.__width):
+
               
             self.__tiles.append(list())
+
         # Sehr einfache Karte basteln:
             for j in range(0, self.__height):
+
         for i in range(0, self.height):
 +
             self.tiles.append(list())
 +
             for j in range(0, self.width):
 
                 if i == 14:
 
                 if i == 14:
                     self.__tiles[i].append("grass")  
+
                     self.tiles[i].append("grass")  
 
                 elif i == 15:
 
                 elif i == 15:
                     self.__tiles[i].append("grass-mud")                  
+
                     self.tiles[i].append("grass-mud")
 
                 elif i > 15:
 
                 elif i > 15:
                     self.__tiles[i].append("mud")
+
                     self.tiles[i].append("mud")
 
                 else:
 
                 else:
                     self.__tiles[i].append("empty")
+
                     self.tiles[i].append("empty")
 
          
 
          
         self.__player = Player.Player()
+
        # Player-Objekt erstellen.
 +
         self.player = Player.Player()
 
                      
 
                      
 
      
 
      
 +
    # Hier rendern wir den sichtbaren Teil der Karte.
 
     def render(self, screen):
 
     def render(self, screen):
         for y in range(0, int(screen.get_height() / self.__tileset.getTileHeight()) + 1):
+
        # Zeilenweise durch die Tiles durchgehen.
             ty = y + self.__cameraY
+
         for y in range(0, int(screen.get_height() / self.tileset.tile_height) + 1):
             if ty >= self.__height or ty < 0:
+
            # Die Kamera Position mit einbeziehen.
 +
             ty = y + self.camera_y
 +
             if ty >= self.height or ty < 0:
 
                 continue
 
                 continue
             col = self.__tiles[ty]
+
             # Die aktuelle Zeile zum einfacheren Zugriff speichern.
             for x in range(0, int(screen.get_width() / self.__tileset.getTileWidth()) + 1):
+
            line = self.tiles[ty]
                 tx = x + self.__cameraX
+
            # Und jetzt spaltenweise die Tiles rendern.
                 if tx >= self.__width or tx < 0:
+
             for x in range(0, int(screen.get_width() / self.tileset.tile_width) + 1):
 +
                # Auch hier müssen wir die Kamera beachten.
 +
                 tx = x + self.camera_x
 +
                 if tx >= self.width or tx < 0:
 
                     continue
 
                     continue
                 tilename = col[tx]
+
                # Wir versuchen, die Daten des Tiles zu bekommen.
                 tile = self.__tileset.getTile(tilename)
+
                 tilename = line[tx]
 +
                 tile = self.tileset.get_tile(tilename)
 +
                # Falls das nicht fehlschlägt können wir das Tile auf die screen-Surface blitten.
 
                 if tile is not None:
 
                 if tile is not None:
                     screen.blit(self.__tileset.getImage(), (x * self.__tileset.getTileWidth(), y * self.__tileset.getTileHeight()), tile.getRect())
+
                     screen.blit(self.tileset.image, (x * self.tileset.tile_width, y * self.tileset.tile_height), tile.rect)
 +
       
 +
        # Und zuletzt den Player rendern.
 +
        self.player.render(screen)
 +
 
 +
       
 +
    # Tastendrücke an den Player weiterreichen:
 +
    def handle_input(self, key):       
 +
        self.player.handle_input(key)
 +
</sourcecode>
 +
 
 +
<tt>Teil_3.py</tt>
 +
<sourcecode lang=python line>
 +
# -*- coding: UTF-8 -*-
 +
 
 +
# Pygame Modul importieren.
 +
import pygame
 +
 
 +
# Unser Tilemap Modul
 +
import Tilemap
 +
 
 +
# Überprüfen, ob die optionalen Text- und Sound-Module geladen werden konnten.
 +
if not pygame.font: print('Fehler pygame.font Modul konnte nicht geladen werden!')
 +
if not pygame.mixer: print('Fehler pygame.mixer Modul konnte nicht geladen werden!')
 +
 
 +
def main():
 +
    # Initialisieren aller Pygame-Module und
 +
    # Fenster erstellen (wir bekommen eine Surface, die den Bildschirm repräsentiert).
 +
    pygame.init()
 +
    screen = pygame.display.set_mode((800, 600))
 
      
 
      
        self.__player.render(screen)
+
    # Titel des Fensters setzen, Mauszeiger nicht verstecken und Tastendrücke wiederholt senden.
                   
+
    pygame.display.set_caption("Pygame-Tutorial: Animation")
 +
    pygame.mouse.set_visible(1)
 +
    pygame.key.set_repeat(1, 30)
 +
 
 +
    # Clock-Objekt erstellen, das wir benötigen, um die Framerate zu begrenzen.
 +
    clock = pygame.time.Clock()
 
      
 
      
     def handleInput(self, key):
+
     # Wir erstellen eine Tilemap.
         self.__player.handleInput(key)
+
    map = Tilemap.Tilemap()
 +
   
 +
    # Die Schleife, und damit unser Spiel, läuft solange running == True.
 +
    running = True
 +
    while running:
 +
         # Framerate auf 30 Frames pro Sekunde beschränken.
 +
        # Pygame wartet, falls das Programm schneller läuft.
 +
        clock.tick(30)
 +
 
 +
        # screen Surface mit Schwarz (RGB = 0, 0, 0) füllen.
 +
        screen.fill((198, 209, 255))
 +
 
 +
        # Alle aufgelaufenen Events holen und abarbeiten.
 +
        for event in pygame.event.get():
 +
            # Spiel beenden, wenn wir ein QUIT-Event finden.
 +
            if event.type == pygame.QUIT:
 +
                running = False
 +
           
 +
            # Wir interessieren uns auch für "Taste gedrückt"-Events.
 +
            if event.type == pygame.KEYDOWN:
 +
                # Wenn Escape gedrückt wird posten wir ein QUIT-Event in Pygames Event-Warteschlange.
 +
                if event.key == pygame.K_ESCAPE:
 +
                    pygame.event.post(pygame.event.Event(pygame.QUIT))
 +
               
 +
                # Alle Tastendrücke auch der Tilemap mitteilen.
 +
                map.handle_input(event.key)
 +
       
 +
        # Die Tilemap auf die screen-Surface rendern.
 +
        map.render(screen)
 +
       
 +
        # Inhalt von screen anzeigen
 +
        pygame.display.flip()
 +
 
 +
 
 +
# Überprüfen, ob dieses Modul als Programm läuft und nicht in einem anderen Modul importiert wird.
 +
if __name__ == '__main__':
 +
    # Unsere Main-Funktion aufrufen.
 +
    main()
 
</sourcecode>
 
</sourcecode>
 +
 +
== Sourcecode herunterladen ==
 +
Du kannst dir den Sourcecode (und die Tileset-Grafik) zu diesem Tutorial [[Media:Pygame-Tutorial.tar.gz|hier herunterladen]]. Unter Windows kannst du die Beispiele starten, indem du doppelt auf die Dateien <tt>Teil_1.py</tt>, <tt>Teil_2.py</tt> und <tt>Teil_3.py</tt> klickst. Funktioniert das nicht, solltest du die Installationen von Python und Pygame nochmal überprüfen.
  
 
== Und jetzt? ==
 
== Und jetzt? ==
Jetzt ist der geeignete Zeitpunkt um mal in die Dokumentation von Pygame zu schauen: http://www.pygame.org/docs/. Denn Pygame kann noch deutlich mehr als das, was wir hier im Tutorial betrachtet haben: http://www.pygame.org/tags/. Wir haben uns aber bereits eine nette Basis für ein kleines Spiel gebastelt, auf die du jetzt aufsetzen kannst.
+
Jetzt ist der geeignete Zeitpunkt, um einen intensiveren Blick in die [http://www.pygame.org/docs/ Dokumentation von Pygame] zu werfen. Denn Pygame kann noch [http://www.pygame.org/tags/ deutlich mehr] als das, was wir hier im Tutorial betrachtet haben. Wir haben uns aber bereits eine nette Basis für ein kleines Spiel gebastelt, auf die du jetzt aufsetzen kannst.
  
 
Also los, Spiele wollen programmiert werden!
 
Also los, Spiele wollen programmiert werden!

Aktuelle Version vom 13. März 2015, 14:32 Uhr

Klicke hier, um diese Version anzusehen.

Meine Werkzeuge
Namensräume
Varianten
Aktionen
Navigation
Werkzeuge