Lernziele
An Hand des folgenden kleinen Spiel-Programmier-Tutorial wollen wir unser Gelerntes festigen. Wir werden dabei unser erlerntes Wissen anwenden.
Quelle Tutorial «Flappy Bird»: Stefan Rothe (opens new window)
# Beschreibung
Flappy Bird wurde 2013 vom vietnamesischen Entwickler Dong Nguyen entwickelt. Es ist ein sehr einfaches Spiel. Der Spieler muss durch wiederholtes Drücken einer Taste den Vogel in der Luft halten und dabei den von rechts kommenden Hindernissen ausweichen.
# Grafiken
Quellen:
- Hintergrund: pintu236, OpenGameArt (opens new window), CC-BY 4.0
- Vogel: bevouliin.com, OpenGameArt (opens new window), CC0
- Röhre: purzen, openclipart (opens new window), CC0
# Objekte
# Hintergrund
Der Hintergrund wird durch einen unbeweglichen Aktor dargestellt. Die Grösse des Fensters wird auf die Grösse des Hintergrundbilds angepasst.
TITLE = "Flappy Bird"
WIDTH = 360
HEIGHT = 640
background = Actor("background")
def draw():
background.draw()
2
3
4
5
6
7
8
9
Aufgabe
Kopieren den obenstehenden Code und erstelle ein Python-Programm mit dem Namen flappy.py (opens new window). Lade die Bilder herunter und speichere sie im richtigen Ordner.
Wenn du das Programm startest, sollte der Hintergrund angezeigt werden.
# Hindernisse
Die Hindernisse werden durch zwei Aktoren pipe_top
und pipe_bottom
dargestellt:
pipe_top = Actor("pipe_top")
pipe_bottom = Actor("pipe_bottom")
2
Die Höhe der Lücke zwischen den Hindernissen ist ein wichtiger Faktor, welches die Schwierigkeit des Spiels beeinflusst. Deshalb definieren wir eine Variable für diesen Wert:
GAP = 160
Um das Spiel interessant zu machen, soll die Position der Lücke zufällig sein. Dazu benötigen wir das Zusatzmodul random
von Python. Es wird mit der Anweisung
import random
in das Programm eingebunden. Nun kann mit der Anweisung
gap_y = random.randrange(a, b)
der Variable gap_y
eine zufällige Zahl zwischen a
und b
zugewiesen werden. Die Lücke soll mindestens um ihre halbe Höhe vom Rand entfernt sein, also wählen wir einen zufälligen Wert zwischen GAP
und HEIGHT - GAP
.
Da die Hindernisse immer wieder neu positioniert werden müssen, werden die entsprechenden Anweisungen in einem Unterprogramm zusammengefasst. Das Unterprogramm soll reset_pipes
heissen:
import random
def reset_pipes():
gap_y = random.randrange(GAP, HEIGHT - GAP)
# Hier Position der Hinternisse setzen
reset_pipes()
2
3
4
5
6
7
Am Ende unseres Programms muss das Unterprogramm mit reset_pipes()
aufgerufen werden, damit die Hindernisse zufällig positioniert werden.
Aufgabe
Integriere den obenstehenden Code in das Programm aus Aufgabe 1. Ergänze das Programm so, dass die zwei Hindernisse zufällig positioniert und dargestellt werden.
Lösung
import random
TITLE = "Flappy Bird"
WIDTH = 360
HEIGHT = 640
GAP = 160
background = Actor("background")
pipe_top = Actor("pipe_top")
pipe_bottom = Actor("pipe_bottom")
def reset_pipes():
pipe_gap_y = random.randint(GAP, HEIGHT - GAP)
pipe_top.bottom = pipe_gap_y - GAP / 2
pipe_bottom.top = pipe_gap_y + GAP / 2
def draw():
background.draw()
pipe_top.draw()
pipe_bottom.draw()
reset_pipes()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# Horizontale Bewegung
Flappy Bird ist ein Side-Scroller. Das heisst, dass sich in horizontaler Richtung nicht die Spielfigur, sondern die «fixen» Objekte bewegen. Hier sind dies die Hindernisse.
Auch die horizontale Geschwindigkeit beeinflusst die Schwierigkeit des Spiels, also definieren wir folgende Variable:
SPEED = 3
Die Bewegung der Hindernisse definieren wir in einem eigenen Unterpgoramm, welches wir move_pipes
nennen. Das Unterprogramm muss aus dem Unterprogramm update
aufgerufen werden.
Wenn die Hindernisse am linken Rand verschwunden sind, sollen sie zurückgesetzt werden, indem das in Aufgabe 2 definierte Unterprogramm reset_pipes
aufgerufen wird:
Aufgabe
- Erstelle ein Unterprogramm
move_pipes
, welches den obenstehenden Ablauf hat. - Erstelle ein Unterprogramm
update
, welches das Unterprogrammmove_pipes
aufruft. - Erweitere das Unterprogramm
reset_pipes
so, dass die Hindernisse ganz nach rechts gesetzt werden, so dass der linke Rand der Hindernisse auf dem rechten Rand des Fenster liegt.
Lösung
import random
TITLE = "Flappy Bird"
WIDTH = 360
HEIGHT = 640
GAP = 160
SPEED = 3
background = Actor("background")
pipe_top = Actor("pipe_top")
pipe_bottom = Actor("pipe_bottom")
def reset_pipes():
pipe_gap_y = random.randint(GAP, HEIGHT - GAP)
pipe_top.bottom = pipe_gap_y - GAP / 2
pipe_bottom.top = pipe_gap_y + GAP / 2
pipe_top.left = WIDTH
pipe_bottom.left = WIDTH
def move_pipes():
pipe_top.left = pipe_top.left - SPEED
pipe_bottom.left = pipe_bottom.left - SPEED
if pipe_top.right < 0:
reset_pipes()
def update():
move_pipes()
def draw():
background.draw()
pipe_top.draw()
pipe_bottom.draw()
reset_pipes()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# Vogel und Schwerkraft
Nun wollen wir endlich den Vogel ins Spiel bringen. Zuerst wird ein Aktor angelegt und horizontal positioniert:
bird = Actor("bird_1")
bird.x = 75
2
Der Vogel unterliegt der Schwerkraft. Deren Stärke ist ein weiterer Einflussfakor auf die Schwierigkeit des Spiels. Wir definieren:
GRAVITY = 0.3
Beim freien Fall des Vogels ändert sich seine Geschwindigkeit. Diese wird in der Variable .vy
des Vogel-Aktors gespeichert. Bei der Bewegung wird erst die Geschwindigkeit um GRAVITY
erhöht, anschliessend wird die vertikale Position .y
um .vy
erhöht.
Mathematische Herleitung
Für die Simulation des freien Falls helfen folgende Überlegungen. Als Ausgangspunkt dienen die Newtonschen Bewegungsgleichungen:
In der Simulation interessiert die jeweilige Änderung der Geschwindigkeit und der Position, also werden die Gleichungen entsprechend umgeformt:
In einem Simulationsschritt kann also die neue Geschwindigkeit und Position so bestimmt werden:
Vereinfachend kann
Aufgabe
- Erzeuge im Programm einen neuen Aktor für den Vogel.
- Erweitere das Unterprogramm
draw
so, dass der Vogel gezeichnet wird. - Erstelle ein Unterprogramm
reset_bird
, welches die vertikale Position auf 200 und die vertikale Geschwindigkeit des Vogels auf 0 setzt. - Erstelle ein Unterprogramm
move_bird
, welches den freien Fall des Vogels gemäss obenstehendem Flussdiagramm modelliert.
Lösung
import random
TITLE = "Flappy Bird"
WIDTH = 360
HEIGHT = 640
GAP = 160
SPEED = 3
GRAVITY = 0.3
background = Actor("background")
pipe_top = Actor("pipe_top")
pipe_bottom = Actor("pipe_bottom")
bird = Actor("bird")
bird.x = 75
def reset_pipes():
pipe_gap_y = random.randint(GAP, HEIGHT - GAP)
pipe_top.bottom = pipe_gap_y - GAP / 2
pipe_bottom.top = pipe_gap_y + GAP / 2
pipe_top.left = WIDTH
pipe_bottom.left = WIDTH
def move_pipes():
pipe_top.left = pipe_top.left - SPEED
pipe_bottom.left = pipe_bottom.left - SPEED
if pipe_top.right < 0:
reset_pipes()
def reset_bird():
bird.y = 200
bird.vy = 0
def move_bird():
bird.vy = bird.vy + GRAVITY
bird.y = bird.y + bird.vy
if bird.top > HEIGHT:
reset_bird()
reset_pipes()
def update():
move_pipes()
move_bird()
def draw():
background.draw()
pipe_top.draw()
pipe_bottom.draw()
bird.draw()
reset_bird()
reset_pipes()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# Fliegen
Der Spieler löst durch das Drücken einer beliebigen Taste einen Flügelschlag aus. Dies soll einerseits durch eine andere Grafik dargestellt werden. Andererseits soll der Vogel dadurch nach oben fliegen, also eine negative vertikale Geschwindigkeit erhalten. Auch diese beeinflusst die Schwierigkeit des Spiels und wird so definiert:
FLAP_STRENGTH = 6.5
Beim Drücken einer Taste wird das Unterprogramm on_key_down
aufgerufen. Also wird dieses im Programm definiert. Dort wird die Geschwindigkeit des Vogels .vy
gesetzt. Ausserdem wird die Grafik des Vogels geändert, indem die Variable .image
entsprechend gesetzt wird:
def on_key_down():
bird.vy = -FLAP_STRENGTH
bird.image = "bird_flap"
2
3
Aufgabe
- Setze
FLAP_STRENGTH
auf den Wert6.5
. - Füge das obenstehende Unterprogramm in dein Programm ein.
- Erweitere das Unterprogramm
move_bird
so, dass der Vogel wieder die Grafik"bird"
erhält, wenn er sich nach unten bewegt, also wenn die Geschwindigkeit.vy
grösser als 0 wird.
Lösung
import random
TITLE = "Flappy Bird"
WIDTH = 360
HEIGHT = 640
GAP = 160
SPEED = 3
GRAVITY = 0.3
FLAP_STRENGTH = 6.5
background = Actor("background")
pipe_top = Actor("pipe_top")
pipe_bottom = Actor("pipe_bottom")
bird = Actor("bird")
bird.x = 75
def reset_pipes():
pipe_gap_y = random.randint(GAP, HEIGHT - GAP)
pipe_top.bottom = pipe_gap_y - GAP / 2
pipe_bottom.top = pipe_gap_y + GAP / 2
pipe_top.left = WIDTH
pipe_bottom.left = WIDTH
def move_pipes():
pipe_top.left = pipe_top.left - SPEED
pipe_bottom.left = pipe_bottom.left - SPEED
if pipe_top.right < 0:
reset_pipes()
def reset_bird():
bird.y = 200
bird.vy = 0
def move_bird():
bird.vy = bird.vy + GRAVITY
if bird.vy > 0:
bird.image = "bird"
bird.y = bird.y + bird.vy
if bird.top > HEIGHT:
reset_bird()
reset_pipes()
def update():
move_pipes()
move_bird()
def on_key_down():
bird.vy = -FLAP_STRENGTH
bird.image = "bird_flap"
def draw():
background.draw()
pipe_top.draw()
pipe_bottom.draw()
bird.draw()
reset_bird()
reset_pipes()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# Kollision und Tod
actor_a.colliderect(actor_b)
überprüft, ob actor_a
mit actor_b
kollidiert. Dies kann als Bedingung für eine Bedingte Ausführung verwendet werden:
if bird.colliderect(pipe_top) or bird.colliderect(pipe_bottom):
bird.image = "bird_dead"
2
Um festzuhalten, dass der Vogel gestorben ist, wird das Bild geändert. Wenn der Vogel gestorben ist, dürfen folgende Anweisungen nicht mehr ausgeführt werden:
- Bewegen auf Tastendruck
- Setzen des «normalen» Bilds, wenn der Vogel sich nach unten bewegt
Dies wird mit einer bedingten Anweisung mit der Bedingung bird.image != "bird_dead"
programmiert, beispielsweise:
def on_key_down():
if bird.image != "bird_dead":
bird.vy = -FLAP_STRENGTH
bird.image = "bird_flap"
2
3
4
Aufgabe
Baue die Kollisionserkennung ein.
Lösung
import random
TITLE = "Flappy Bird"
WIDTH = 360
HEIGHT = 640
GAP = 160
SPEED = 3
GRAVITY = 0.3
FLAP_STRENGTH = 6.5
background = Actor("background")
pipe_top = Actor("pipe_top")
pipe_bottom = Actor("pipe_bottom")
bird = Actor("bird")
bird.x = 75
def reset_pipes():
pipe_gap_y = random.randint(GAP, HEIGHT - GAP)
pipe_top.bottom = pipe_gap_y - GAP / 2
pipe_bottom.top = pipe_gap_y + GAP / 2
pipe_top.left = WIDTH
pipe_bottom.left = WIDTH
def move_pipes():
pipe_top.left = pipe_top.left - SPEED
pipe_bottom.left = pipe_bottom.left - SPEED
if pipe_top.right < 0:
reset_pipes()
def reset_bird():
bird.y = 200
bird.vy = 0
bird.image = "bird"
def move_bird():
bird.vy = bird.vy + GRAVITY
if bird.vy > 0 and bird.image != "bird_dead":
bird.image = "bird"
bird.y = bird.y + bird.vy
if bird.top > HEIGHT:
reset_bird()
reset_pipes()
def update():
move_pipes()
move_bird()
if bird.colliderect(pipe_top) or bird.colliderect(pipe_bottom):
bird.image = "bird_dead"
def on_key_down():
if bird.image != "bird_dead":
bird.vy = -FLAP_STRENGTH
bird.image = "bird_flap"
def draw():
background.draw()
pipe_top.draw()
pipe_bottom.draw()
bird.draw()
reset_bird()
reset_pipes()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# Punkte zählen
Aufgabe
Versuche einen Punktestand zu zählen und anzuzeigen.
Lösung
import random
TITLE = "Flappy Bird"
WIDTH = 360
HEIGHT = 640
GAP = 160
SPEED = 3
GRAVITY = 0.3
FLAP_STRENGTH = 6.5
background = Actor("background")
pipe_top = Actor("pipe_top")
pipe_bottom = Actor("pipe_bottom")
bird = Actor("bird")
bird.x = 75
bird.max_score = 0
def reset_pipes():
pipe_gap_y = random.randint(GAP, HEIGHT - GAP)
pipe_top.bottom = pipe_gap_y - GAP / 2
pipe_bottom.top = pipe_gap_y + GAP / 2
pipe_top.left = WIDTH
pipe_bottom.left = WIDTH
def move_pipes():
pipe_top.left = pipe_top.left - SPEED
pipe_bottom.left = pipe_bottom.left - SPEED
if pipe_top.right < 0:
reset_pipes()
bird.score = bird.score + 1
bird.max_score = max(bird.score, bird.max_score)
def reset_bird():
bird.y = 200
bird.vy = 0
bird.image = "bird"
bird.score = 0
def move_bird():
bird.vy = bird.vy + GRAVITY
if bird.vy > 0 and bird.image != "bird_dead":
bird.image = "bird"
bird.y = bird.y + bird.vy
if bird.top > HEIGHT:
reset_bird()
reset_pipes()
def update():
move_pipes()
move_bird()
if bird.colliderect(pipe_top) or bird.colliderect(pipe_bottom):
bird.image = "bird_dead"
def on_key_down():
if bird.image != "bird_dead":
bird.vy = -FLAP_STRENGTH
bird.image = "bird_flap"
def draw():
background.draw()
pipe_top.draw()
pipe_bottom.draw()
bird.draw()
screen.draw.text(str(bird.score), left=20, top=20, fontsize=60)
screen.draw.text(str(bird.max_score), left=20, top=70, fontsize=60)
reset_bird()
reset_pipes()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79