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:

# Objekte

Die Spielwelt von «Flappy Bird»

# 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()
1
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")
1
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
1

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
1

in das Programm eingebunden. Nun kann mit der Anweisung

gap_y = random.randrange(a, b)
1

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()
1
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()
1
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
1

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:

«Bewegung der Hindernisse»

Aufgabe

  1. Erstelle ein Unterprogramm move_pipes, welches den obenstehenden Ablauf hat.
  2. Erstelle ein Unterprogramm update, welches das Unterprogramm move_pipes aufruft.
  3. 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()
1
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
1
2

Der Vogel unterliegt der Schwerkraft. Deren Stärke ist ein weiterer Einflussfakor auf die Schwierigkeit des Spiels. Wir definieren:

GRAVITY = 0.3
1

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 gewählt werden. Somit ergibt sich:

«Bewegung des Vogels»

Aufgabe

  1. Erzeuge im Programm einen neuen Aktor für den Vogel.
  2. Erweitere das Unterprogramm draw so, dass der Vogel gezeichnet wird.
  3. Erstelle ein Unterprogramm reset_bird, welches die vertikale Position auf 200 und die vertikale Geschwindigkeit des Vogels auf 0 setzt.
  4. 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()
1
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
1

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"
1
2
3

Aufgabe

  1. Setze FLAP_STRENGTH auf den Wert 6.5.
  2. Füge das obenstehende Unterprogramm in dein Programm ein.
  3. 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()
1
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)
1

ü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"
1
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"
1
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()
1
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()
1
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
Letzte Änderung: 26.6.2020, 20:40:54