Zuerst brauchen wir einen Startpunkt. Heißt wir brauchen einen Bildschirm, eine Welt und ein paar Charaktere und Objekte. So ein Standard-Grundgerüst haben wir im main_template vorgegeben, dieses könnt ihr benutzen. Das funktioniert so:
Zuerst brauchen wir einen Startpunkt. Heißt wir brauchen einen Bildschirm, eine Welt und ein paar Charaktere und Objekte. So ein Standard-Grundgerüst haben wir im main_template vorgegeben, dieses könnt ihr benutzen. Das funktioniert so:
1. Ein *screen* ist der Rahmen des Spiels und hat eine definierte Höhe und Breite (Wir benutzen englische Begriffe daher -> *width* und *height*).
2. Als 2D-Welt wird eine *Tile-Map* geladen. Das ist eine Karte, die wir mit Hilfe des Programmes *Tiled* erstellt haben.
1. Ein _screen_ ist der Rahmen des Spiels und hat eine definierte Höhe und Breite (Wir benutzen englische Begriffe daher -> _width_ und _height_).
3. Um Objekte (*Sprites*) zusammenzufassen, werden *Sprite-Gruppen* erstellt. So kann z.B. allgemein definiert werden, was bei einer Kollision zwischen Sprites der Gruppe *Player* und *Enemy* passiert, und muss dies nicht für jeden einzelnen Sprite tun.
2. Als 2D-Welt wird eine _Tile-Map_ geladen. Das ist eine Karte, die wir mit Hilfe des Programmes _Tiled_ erstellt haben.
4. In der settings.py kann eingestellt werden, ob es Scrolling geben soll (kein Autoscrolling), welche Schriftart für Texte benutzt werden sollen und ob es Gravitation gibt (bei Rennspielen braucht man beispielsweise keine).
3. Um Objekte (_Sprites_) zusammenzufassen, werden _Sprite-Gruppen_ erstellt. So kann z.B. allgemein definiert werden, was bei einer Kollision zwischen Sprites der Gruppe _Player_ und _Enemy_ passiert, und muss dies nicht für jeden einzelnen Sprite tun.
4. In der settings.py kann eingestellt werden, ob es Scrolling geben soll (kein Autoscrolling), welche Schriftart für Texte benutzt werden sollen und ob es Gravitation gibt (bei Rennspielen braucht man beispielsweise keine).
## Sprites
## Sprites
### Python-Wissen: Klassen
### Python-Wissen: Klassen
In Python, und vielen anderen Programmiersprachen, gibt es Klassen. Klassen beschreiben, welche Eigenschaften und Funktionen Objekte dieser Klasse haben. Klassen sind quasi wie ein Bauplan aus dem man dann mehrmals dasselbe Objekt herstellen kann. Von diesem Bauplan kann man mit Hilfe von *Parametern* abweichen, zum Beispiel kann man jedes mal ein anderes Bild einfügen. Gibt es mehrere Klassen, die viele gemeinsame Eigenschaften haben, ist es sinnvoll, eine übergeordnete Klasse (auch *Super-Klasse*) mit den allgemeinenen Eigenschaften und Kinderklassen mit den speziellen Eigenschaften zu erstellen.
In Python, und vielen anderen Programmiersprachen, gibt es Klassen. Klassen beschreiben, welche Eigenschaften und Funktionen Objekte dieser Klasse haben. Klassen sind quasi wie ein Bauplan aus dem man dann mehrmals dasselbe Objekt herstellen kann. Von diesem Bauplan kann man mit Hilfe von _Parametern_ abweichen, zum Beispiel kann man jedes mal ein anderes Bild einfügen. Gibt es mehrere Klassen, die viele gemeinsame Eigenschaften haben, ist es sinnvoll, eine übergeordnete Klasse (auch _Super-Klasse_) mit den allgemeinenen Eigenschaften und Kinderklassen mit den speziellen Eigenschaften zu erstellen.
Für uns heißt das: Die Klasse *_Character* hat die Parameter *location* (Spawnkoordinaten), *scale* (Größe) und *image* (gewünschtes Bild). Die *Player* im Spiel, als auch die *Enemies*, sind solche *_Character*, daher haben sie alles auch diese Attribute. Es gibt aber auch Unterschiede, z.B. lassen sich *Player* steuern, *Enemies* nicht. Hier kommt das Konzept der *Vererbung* ins Spiel. Die gemeinsamen Eigenschaften sind in der Klasse *_Character* definiert. Darauf basierend gibt es die Klassen *LinearPlayer* und *AnimatedCoin*, welche beide von der Klasse *_Character* erben. Das bedeutet, dass sie deren Eigenschaften ebenfalls haben, und zusätzlich neue Eigenschaften und Funktionen definieren können. Am Ende übergibt man also der Klasse *Player* die vier *Parameter*, die die *_Character*-Klasse braucht und ein weiteres.
Für uns heißt das: Die Klasse _\_Character_ hat die Parameter _location_ (Spawnkoordinaten), _scale_ (Größe) und _image_ (gewünschtes Bild). Die _Player_ im Spiel, als auch die _Enemies_, sind solche _\_Character_, daher haben sie alles auch diese Attribute. Es gibt aber auch Unterschiede, z.B. lassen sich _Player_ steuern, _Enemies_ nicht. Hier kommt das Konzept der _Vererbung_ ins Spiel. Die gemeinsamen Eigenschaften sind in der Klasse _\_Character_ definiert. Darauf basierend gibt es die Klassen _LinearPlayer_ und _AnimatedCoin_, welche beide von der Klasse _\_Character_ erben. Das bedeutet, dass sie deren Eigenschaften ebenfalls haben, und zusätzlich neue Eigenschaften und Funktionen definieren können. Am Ende übergibt man also der Klasse _Player_ die vier _Parameter_, die die _\_Character_-Klasse braucht und ein weiteres.
### Wie fügt ich selber Sprites ein?
### Wie fügt ich selber Sprites ein?
Um selber einen Sprite hinzuzufügen, müssen wir erstmal eine passende Klasse, also einen passenden Bauplan finden. Folgende gibt es schon vorgefertigt:
Um selber einen Sprite hinzuzufügen, müssen wir erstmal eine passende Klasse, also einen passenden Bauplan finden. Folgende gibt es schon vorgefertigt:
```mermaid
```mermaid
graph TD;
graph TD;
A(Was will ich erstellen?)-->B(Spieler);
A(Was will ich erstellen?)-->B(Spieler);
A(Was will ich erstellen?)-->C(Gegner);
A(Was will ich erstellen?)-->C(Gegner);
A(Was will ich erstellen?)-->D(Objekte);
A(Was will ich erstellen?)-->D(Objekte);
B(Spieler)-->E(Der Spieler soll sich in eine Richtung bewegen);
B(Spieler)-->E(Der Spieler soll sich in eine Richtung bewegen);
E(Der Spieler soll sich in eine Richtung bewegen)-->G(LinearPlayer);
E(Der Spieler soll sich in eine Richtung bewegen)-->G(LinearPlayer);
Jedes Objekt braucht unterschiedliche Parameter, wie oben beschrieben. Hier sind sie jeweils aufgelistet:
Jedes Objekt braucht unterschiedliche Parameter, wie oben beschrieben. Hier sind sie jeweils aufgelistet:
* Beide Spieler
- Beide Spieler
1. Spawnkoordinaten (x, y)
1. Spawnkoordinaten (x, y)
2. Gewünschte Größe in pixel (Breite, Höhe)
2. Gewünschte Größe in pixel (Breite, Höhe)
3. Dateiname des Bildes (mit Dateiendung)
3. Dateiname des Bildes (mit Dateiendung)
4. Genutzte Karte (standardmäßig tilemap)
4. Genutzte Karte (standardmäßig tilemap)
5. Spritegroup der Wände (standardmäßig platforms)
5. Spritegroup der Wände (standardmäßig platforms)
* Gegner
- Gegner
1. Spawnkoordinaten (x, y)
1. Spawnkoordinaten (x, y)
2. Gewünschte Größe in pixel (Breite, Höhe)
2. Gewünschte Größe in pixel (Breite, Höhe)
3. Dateiname des Bildes (mit Dateiendung)
3. Dateiname des Bildes (mit Dateiendung)
...
@@ -68,20 +68,23 @@ Jedes Objekt braucht unterschiedliche Parameter, wie oben beschrieben. Hier sind
...
@@ -68,20 +68,23 @@ Jedes Objekt braucht unterschiedliche Parameter, wie oben beschrieben. Hier sind
5. Spritegroup der Wände (standardmäßig platforms)
5. Spritegroup der Wände (standardmäßig platforms)
6. Die Geschwindigkeit des Gegners
6. Die Geschwindigkeit des Gegners
7. Entweder:
7. Entweder:
* Die Richtung der Bewegung ([speedx, speedy]) (Bei LinearEnemy)
- Die Richtung der Bewegung ([speedx, speedy]) (Bei LinearEnemy)
* Den Spieler der verfolgt werden soll (meist player) (Bei FollowingEnemy)
- Den Spieler der verfolgt werden soll (meist player) (Bei FollowingEnemy)
* Wie oft die Richtung geändert werden soll (in Sekunden) (Bei RandomEnemy)
- Wie oft die Richtung geändert werden soll (in Sekunden) (Bei RandomEnemy)
* Objekte
- Objekte
1. Spawnkoordinaten (x, y)
2. Größe des Bildes (Breite, Höhe)
1. Spawnkoordinaten (x, y)
3. Dateiname des Bildes (mit Dateiendung)
2. Größe des Bildes (Breite, Höhe)
4. Bei Animationen: Größe des einzelnen Sprites
3. Dateiname des Bildes (mit Dateiendung)
* Projektile
4. Bei Animationen: Größe des einzelnen Sprites
1. Spawnkoordinaten (bei schießender Figur: rect.topleft/topright der Figur)
2. Variable in der Bewegung gespeichert ist (bei schießender Figur: z.B. player.move oder enemy.move) oder Winkel des Pfeils (bei AngleProjectile. Bei schießendem Spieler player.angle)
- Projektile
3. Größe des Bildes [x, y]
4. Dateiname des Bildes (mit Dateiendung)
1. Spawnkoordinaten (bei schießender Figur: rect.topleft/topright der Figur)
5. Geschwindigkeit der Pfeile
2. Variable in der Bewegung gespeichert ist (bei schießender Figur: z.B. player.move oder enemy.move) oder Winkel des Pfeils (bei AngleProjectile. Bei schießendem Spieler player.angle)
3. Größe des Bildes [x, y]
4. Dateiname des Bildes (mit Dateiendung)
5. Geschwindigkeit der Pfeile
Wenn man diese Parameter übergibt sieht das etwa so aus:
Wenn man diese Parameter übergibt sieht das etwa so aus:
...
@@ -96,7 +99,7 @@ platforms -> Die Spritegruppe der Plattformen und Wände
...
@@ -96,7 +99,7 @@ platforms -> Die Spritegruppe der Plattformen und Wände
"""
"""
```
```
Man kann aber auch etwas tricksen. In Tiled kann man nämlich auch einfach ein Objekt auf der Objektebene hinzufügen, das entsprechend bennenen und dann die Koordinaten und Größe des Objekts im Code verwenden.
Man kann aber auch etwas tricksen. In Tiled kann man nämlich auch einfach ein Objekt auf der Objektebene hinzufügen, das entsprechend bennenen und dann die Koordinaten und Größe des Objekts im Code verwenden.
Dazu muss man folgendes machen:
Dazu muss man folgendes machen:
...
@@ -110,7 +113,7 @@ class Coin(_Object):
...
@@ -110,7 +113,7 @@ class Coin(_Object):
2. Erstelle Objekte in Tiled mit gleichen Namen
2. Erstelle Objekte in Tiled mit gleichen Namen
3. Erstelle eine Spritegruppe und nutze eine *for-Schleife* um alle Objekte mit dem Namen der Gruppe hinzuzufügen. Dann kannst du sie zeichnen
3. Erstelle eine Spritegruppe und nutze eine _for-Schleife_ um alle Objekte mit dem Namen der Gruppe hinzuzufügen. Dann kannst du sie zeichnen
```python
```python
coins=pygame.sprite.RenderClear()# Erstellen der Spritegruppe
coins=pygame.sprite.RenderClear()# Erstellen der Spritegruppe
...
@@ -127,7 +130,7 @@ def update():
...
@@ -127,7 +130,7 @@ def update():
## Wie zeichne ich auf den Bildschirm?
## Wie zeichne ich auf den Bildschirm?
Ähnlich zu einem Film, der aus vielen Bildern (*Frames*) besteht, funktioniert auch PyGame. Erst wird eine einfarbige Fläche erstellt, auf die dann die Sprites vorgezeichnet werden und wenn alles fertig wird, wird es auf einmal gemalt. Dabei gilt:
Ähnlich zu einem Film, der aus vielen Bildern (_Frames_) besteht, funktioniert auch PyGame. Erst wird eine einfarbige Fläche erstellt, auf die dann die Sprites vorgezeichnet werden und wenn alles fertig wird, wird es auf einmal gemalt. Dabei gilt:
Der Code wird von oben nach unten gelesen. Daher muss sie immer diesem Aufbau folgen:
Der Code wird von oben nach unten gelesen. Daher muss sie immer diesem Aufbau folgen:
...
@@ -135,22 +138,22 @@ Der Code wird von oben nach unten gelesen. Daher muss sie immer diesem Aufbau fo
...
@@ -135,22 +138,22 @@ Der Code wird von oben nach unten gelesen. Daher muss sie immer diesem Aufbau fo
defupdate():
defupdate():
screen.fill((0,0,0))# Das füllt den Bildschirm mit einer Farbe. Daher bitte immer zuerst.
screen.fill((0,0,0))# Das füllt den Bildschirm mit einer Farbe. Daher bitte immer zuerst.
screen.blit(tilemap_image,(0,0))# Auf den schwarzen Bildschirm wird die Karte gemalt.
screen.blit(tilemap_image,(0,0))# Auf den schwarzen Bildschirm wird die Karte gemalt.
# Hier werden Charaktere und Objekte "vorgezeichnet". Da ist die Reihenfolge relativ egal.
# Hier werden Charaktere und Objekte "vorgezeichnet". Da ist die Reihenfolge relativ egal.
Das findet für jeden Frame erneut statt und steht in der *update-Funktion*, die immer wieder aufgerufen wird.
Das findet für jeden Frame erneut statt und steht in der _update-Funktion_, die immer wieder aufgerufen wird.
Damit in dem Spiel etwas passiert und nicht immer das gleiche Bild entsteht, muss zwischen den Aufrufen der *update-Funktion* noch etwas geschehen: Spiel-Logik.
Damit in dem Spiel etwas passiert und nicht immer das gleiche Bild entsteht, muss zwischen den Aufrufen der _update-Funktion_ noch etwas geschehen: Spiel-Logik.
## Die Spiel-Logik
## Die Spiel-Logik
Ein interaktives Spiel braucht eine Steuerung. Vor jedem *update*-Aufruf wird geprüft, ob gerade eine Pfeiltaste gedrückt ist. Wenn das so ist, wird die Position des Players verändert, sodass die Figur im nächsten Frame eine andere Position hat.
Ein interaktives Spiel braucht eine Steuerung. Vor jedem _update_-Aufruf wird geprüft, ob gerade eine Pfeiltaste gedrückt ist. Wenn das so ist, wird die Position des Players verändert, sodass die Figur im nächsten Frame eine andere Position hat.
```python
```python
foreventinpygame.event.get():#Jedes Event wird in jedem Frame geprüft
foreventinpygame.event.get():#Jedes Event wird in jedem Frame geprüft
...
@@ -158,7 +161,7 @@ for event in pygame.event.get(): #Jedes Event wird in jedem Frame geprüft
...
@@ -158,7 +161,7 @@ for event in pygame.event.get(): #Jedes Event wird in jedem Frame geprüft
exit(0)
exit(0)
ifevent.type==pygame.KEYDOWN:# Hier wird geprüft ob irgendeine Taste gedrückt wurde.
ifevent.type==pygame.KEYDOWN:# Hier wird geprüft ob irgendeine Taste gedrückt wurde.
ifevent.key==pygame.K_LSHIFT:# Danach schaut man, *welche* Taste das ist.
ifevent.key==pygame.K_LSHIFT:# Danach schaut man, *welche* Taste das ist.
player.running=True
player.running=True
ifevent.type==pygame.KEYUP:# Hier wird geprüft ob irgendeine Taste losgelassen wurde.
ifevent.type==pygame.KEYUP:# Hier wird geprüft ob irgendeine Taste losgelassen wurde.
ifevent.key==pygame.K_LSHIFT:# Danach schaut man wieder, welche Taste genau.
ifevent.key==pygame.K_LSHIFT:# Danach schaut man wieder, welche Taste genau.
player.running=False
player.running=False
...
@@ -171,54 +174,53 @@ if (pygame.sprite.spritecollide(player, coins, True)):
...
@@ -171,54 +174,53 @@ if (pygame.sprite.spritecollide(player, coins, True)):
player.score+=100
player.score+=100
```
```
### Wie ändere ich die vorgegebenen Templates?
### Wie ändere ich die vorgegebenen Templates?
Ein großer Teil der Arbeit besteht darin, das was es schon gibt, auf die eigenen Bedürfnisse anzupassen. Sagen wir mal, wir wollen, dass Spieler nicht nur mit WASD steuerbar ist sondern auch mit den Pfeiltasten. Dann tun wir folgendes:
Ein großer Teil der Arbeit besteht darin, das was es schon gibt, auf die eigenen Bedürfnisse anzupassen. Sagen wir mal, wir wollen, dass Spieler nicht nur mit WASD steuerbar ist sondern auch mit den Pfeiltasten. Dann tun wir folgendes:
1. Wir erstellen eine neue *Klasse* (kein Objekt!) mit der Vorlage, die wir wollen (hier LinearPlayer)
1. Wir erstellen eine neue _Klasse_ (kein Objekt!) mit der Vorlage, die wir wollen (hier LinearPlayer)
```python
```python
classPlayer(LinearPlayer):# Wir erstellen einen neue Klasse, die von *LinearPlayer* erbt
classPlayer(LinearPlayer):# Wir erstellen einen neue Klasse, die von *LinearPlayer* erbt
super().__init__(self,location,size,image,tilemap,walls)# Diese Klasse hat exakt dieselben Eigenschaften wie die Vorlage (=Vererbung). Mit super() erhält man die Super-Klasse, also LinearPlayer
super().__init__(self,location,size,image,tilemap,walls)# Diese Klasse hat exakt dieselben Eigenschaften wie die Vorlage (=Vererbung). Mit super() erhält man die Super-Klasse, also LinearPlayer
```
```
Nun können wir die Klasse verändern oder neue Dinge hinzufügen.
Nun können wir die Klasse verändern oder neue Dinge hinzufügen.
2. Bestehendes Abändern
2. Bestehendes Abändern
```python
```python
classPlayer(LinearPlayer):# Wir erstellen einen neue Klasse, die von *LinearPlayer* erbt
classPlayer(LinearPlayer):# Wir erstellen einen neue Klasse, die von *LinearPlayer* erbt
super().__init__(self,location,size,image,tilemap,walls)# Diese Klasse hat exakt dieselben Eigenschaften wie die Vorlage (=Vererbung)
super().__init__(self,location,size,image,tilemap,walls)# Diese Klasse hat exakt dieselben Eigenschaften wie die Vorlage (=Vererbung)
self.keys={# self.keys ist schon in der übergeordneten Klasse definiert. Das überschreiben wir hier.
self.keys={# self.keys ist schon in der übergeordneten Klasse definiert. Das überschreiben wir hier.
"up":pygame.K_UP,
"up":pygame.K_UP,
"down":pygame.K_DOWN,
"down":pygame.K_DOWN,
"left":pygame.K_LEFT,
"left":pygame.K_LEFT,
"right":pygame.K_RIGHT,#Hier standen vorher die Kasten W, A, S und D. Eine Übersicht über alle Tasten ist hier zu finden: https://www.pygame.org/docs/ref/event.html
"right":pygame.K_RIGHT,#Hier standen vorher die Kasten W, A, S und D. Eine Übersicht über alle Tasten ist hier zu finden: https://www.pygame.org/docs/ref/event.html
"jump":pygame.K_SPACE
"jump":pygame.K_SPACE
}
}
```
```
3. Neues hinzufügen
3. Neues hinzufügen
```python
```python
classPlayer(LinearPlayer):# Wir erstellen einen neue Klasse, die von *LinearPlayer* erbt
classPlayer(LinearPlayer):# Wir erstellen einen neue Klasse, die von *LinearPlayer* erbt
super().__init__(self,location,size,image,tilemap,walls)# Diese Klasse hat exakt dieselben Eigenschaften wie die Vorlage (=Vererbung)
super().__init__(self,location,size,image,tilemap,walls)# Diese Klasse hat exakt dieselben Eigenschaften wie die Vorlage (=Vererbung)
self.keys={# self.keys ist schon in der übergeordneten Klasse definiert. Das überschreiben wir hier.
self.keys={# self.keys ist schon in der übergeordneten Klasse definiert. Das überschreiben wir hier.
"up":pygame.K_UP,
"up":pygame.K_UP,
"down":pygame.K_DOWN,
"down":pygame.K_DOWN,
"left":pygame.K_LEFT,
"left":pygame.K_LEFT,
"right":pygame.K_RIGHT,#Hier standen vorher die Kasten W, A, S und D. Eine Übersicht über alle Tasten ist hier zu finden: https://www.pygame.org/docs/ref/event.html
"right":pygame.K_RIGHT,#Hier standen vorher die Kasten W, A, S und D. Eine Übersicht über alle Tasten ist hier zu finden: https://www.pygame.org/docs/ref/event.html
"jump":pygame.K_SPACE,
"jump":pygame.K_SPACE,
"say_hi":pygame.K_9
"say_hi":pygame.K_9
}
}
defsay_hi(self,sound):# Ab hier beginnt eine neue Funktion
defsay_hi(self,sound):# Ab hier beginnt eine neue Funktion
foreventinevents:
foreventinevents:
ifevent.type==pygame.KEYDOWN:
ifevent.type==pygame.KEYDOWN:
...
@@ -236,20 +238,20 @@ Es gibt aber nicht nur ganze Klassen sondern auch einzelne Funktionen. Also anst
...
@@ -236,20 +238,20 @@ Es gibt aber nicht nur ganze Klassen sondern auch einzelne Funktionen. Also anst
## Timer
## Timer
Manchmal braucht man einen Timer, zum Beispiel um einen Gegner spawnen zu lassen oder um die Zeit im Spiel anzuzeigen.
Manchmal braucht man einen Timer, zum Beispiel um einen Gegner spawnen zu lassen oder um die Zeit im Spiel anzuzeigen.
Die Zeit in Sekunden wird mithilfe der Funktion `time()` ausgegeben.
Die Zeit in Sekunden wird mithilfe der Funktion `time()` ausgegeben.
## Musik & Sound
## Musik & Sound
Zwischen Musik und Sound gibt es ein paar feine Unterschiede.
Zwischen Musik und Sound gibt es ein paar feine Unterschiede.
| Kann nur eine Datei abgespielt werden | Können mehrere gleichzeitig existieren |
| Kann nur eine Datei abgespielt werden | Können mehrere gleichzeitig existieren |
| Wiederholt sich | Wiederholt sich (eigentlich) nicht |
| Wiederholt sich | Wiederholt sich (eigentlich) nicht |
| Startet sofort | Muss erst definiert werden und dann abgespielt |
| Startet sofort | Muss erst definiert werden und dann abgespielt |
Musik wird mit der Funktion *add_music()* gestartet. Diese benötigt die Parameter *filename* und wie oft es sich wiederholen soll. -1 ist eine unendliche Wiederholung. Die Datei muss im Verzeichnis ./sound/ sein.
Musik wird mit der Funktion _add_music()_ gestartet. Diese benötigt die Parameter _filename_ und wie oft es sich wiederholen soll. -1 ist eine unendliche Wiederholung. Die Datei muss im Verzeichnis ./sound/ sein.
Sound muss erst definiert werden um ihn dann zu einem späteren Zeitpunkt abzuspielen. Ein Sound kann auch öfter benutzt werden.
Sound muss erst definiert werden um ihn dann zu einem späteren Zeitpunkt abzuspielen. Ein Sound kann auch öfter benutzt werden.
...
@@ -266,17 +268,16 @@ sound.play()
...
@@ -266,17 +268,16 @@ sound.play()
sound.play()
sound.play()
```
```
## Text
## Text
Oft muss man Text auf den Bildschirm spielen. Zum Beispiel um einen Score auf dem Bildschirm zu zeigen.
Oft muss man Text auf den Bildschirm spielen. Zum Beispiel um einen Score auf dem Bildschirm zu zeigen.
```python
```python
defupdate():
defupdate():
screen.blit(add_text(text,size,color,bold),(x,y))
screen.blit(add_text(text,size,color,bold),(x,y))
```
```
Was passiert hier genau? Zuerst wird der Text selbst erstellt. Dazu braucht die `add_text`-Funktion den "Text", die Schriftgröße, die Farbe in RGB (R, G, B) und die Angabe, ob der Text fett sein soll. Danach wird gesagt, *wo* der Text erscheinen soll.
Was passiert hier genau? Zuerst wird der Text selbst erstellt. Dazu braucht die `add_text`-Funktion den "Text", die Schriftgröße, die Farbe in RGB (R, G, B) und die Angabe, ob der Text fett sein soll. Danach wird gesagt, _wo_ der Text erscheinen soll.
```python
```python
defupdate():
defupdate():
...
@@ -286,7 +287,7 @@ def update():
...
@@ -286,7 +287,7 @@ def update():
Man kann auch den Text mit Variablen kombinieren. Sollen die Leben des Spielers (Hier gespeichert in player.lives) angezeigt werden, würde man folgenden Text nutzen:
Man kann auch den Text mit Variablen kombinieren. Sollen die Leben des Spielers (Hier gespeichert in player.lives) angezeigt werden, würde man folgenden Text nutzen:
```python
```python
(f"Lives: {player.lives}")
f"Lives: {player.lives}"
```
```
Die Schriftgröße kann in der settings.py editiert werden.
Die Schriftgröße kann in der settings.py editiert werden.
...
@@ -309,4 +310,4 @@ rotate_image("Dateiname des Bildes", (Zahl um wie viel Grad das Bild gedreht wer
...
@@ -309,4 +310,4 @@ rotate_image("Dateiname des Bildes", (Zahl um wie viel Grad das Bild gedreht wer
## Neustarten des Programmes
## Neustarten des Programmes
Code immer wieder zu stoppen und zu starten kann nervig sein. Daher kann die `retry()` ein Programm einfacher neustarten. Dabei braucht sie immer `__file__` als *Parameter*. Das hat einen ganz einfachen Grund: Die Funktion ruft einen neuen Prozess auf, der die ganze Datei neustartet und daher den genauen Dateipfad braucht. Deswegen ist es auch ganz wichtig, dass die Funktion nicht dazu benutzt wird, um von einem Chekpoint neuzustarten, das wird nicht funktionieren. Nur wenn man das ganze Programm neustarten will, ist das eine Option.
Code immer wieder zu stoppen und zu starten kann nervig sein. Daher kann die `retry()` ein Programm einfacher neustarten. Dabei braucht sie immer `__file__` als _Parameter_. Das hat einen ganz einfachen Grund: Die Funktion ruft einen neuen Prozess auf, der die ganze Datei neustartet und daher den genauen Dateipfad braucht. Deswegen ist es auch ganz wichtig, dass die Funktion nicht dazu benutzt wird, um von einem Chekpoint neuzustarten, das wird nicht funktionieren. Nur wenn man das ganze Programm neustarten will, ist das eine Option.