Post

Spieleentwicklung mit PyGame - Sprites

Spieleentwicklung mit PyGame - Sprites

Animierte Sprites

Slime-Animation-Frames

Etwas Hintergrund

Jump’n Runs und Plattformer gibt es schon ewig. Wichtige Technologien/gute Spiele, über die wir gesprochen haben, sind:

  • Parallax-Scrolling (z.B. im ersten Rayman Spiel, Halloween Harry, Sonic, CastleVania…)
  • Double-Bufferung/V-Sync
  • 3D Plattformer (z.B. Metroid Dread, F.I.S.T, Prince of Persia Lost Crown, Rayman Legends, …)

Terminologie

Sprites:

In Jump ’n’ Runs (und der gesamten 2D-Videospielentwicklung) bezeichnet der Begriff Sprite ein zwei- oder mehrdimensionales Grafikobjekt, das sich unabhängig vom restlichen Bildschirmhintergrund bewegen und verändern kann. Der Begriff stammt historisch aus der Computer- und Konsolengrafik (wie beim NES oder Commodore 64) und leitet sich vom englischen Wort für „Kobold“ oder „Geist“ ab, da diese Objekte scheinbar schwerelos über den Bildschirm „schweben“.

Frame:

Bezeichnet ein einzelnes Bild, wobei damit der gesamte Bildschirm bezeichnet werden kann (z.B. in Frames per Second/FPS als Angabe der Zeichen-/Aktualisierungsgeschwindigkeit), oder auch ein einzelner (Zeichen-/Bild-)-Schritt einer Animation.

Tools

Online Sprite-Animations/Pixel-Art-Editoren:

  • Piscal https://www.piskelapp.com
  • Tsabu https://tsubu.art
  • Pixelart https://www.pixilart.com

Die sich ergebenden einzelnen Frame-Bilder einer Animation können entweder in einzelnen Dateien abgelegt werden, oder gerastert zusammen in einer einzelnen Datei, wie bspw. die Animation von Justus:

Slime-Animation-Frames

In diesem Fall sind es 9 Frames in einem 4 Spaltenraster, wobei jeder Frame 32x32 Pixel Auflösung hat.

PyGame Basics

Die Installation von PyGame und die Einrichtung der PyCharm Entwicklungsumgebung im Computerkabinet ist im Post PyCharm und PyGame Grundlagen beschrieben.

Sprite-Animation in PyGame

Hier ist der ausführlich dokumentierte Quelltext des Beispiels:

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
# =============================================================================
#  Slime-Animation – ein kleines Spiel mit PyGame
# =============================================================================
#
#  Was macht dieses Programm?
#  --------------------------
#  Es öffnet ein Fenster, in dem ein kleiner grüner Schleim (engl. "Slime")
#  auf einem Boden steht. Der Schleim wackelt die ganze Zeit (Animation) und
#  du kannst ihn mit den Pfeiltasten steuern:
#
#      Pfeil links   ->  nach links laufen
#      Pfeil rechts  ->  nach rechts laufen
#      Pfeil hoch    ->  springen
#
#  Das Fenster schließt du ganz normal über das "X" oben in der Ecke.
#
#  Wie starte ich das Programm?
#  ----------------------------
#  1. PyGame muss installiert sein:      pip install pygame
#  2. Im Ordner dieses Skripts starten:  python3 slime_animation.py
#     (Wichtig: Der Unterordner "images" mit dem Bild "Enemy1_slime.png"
#      muss daneben liegen, sonst findet das Programm das Bild nicht.)
#
#  Ein paar Python-Grundlagen vorweg
#  ---------------------------------
#  - Alles hinter einem "#" ist ein KOMMENTAR. Python ignoriert das, es ist
#    nur eine Notiz für uns Menschen.
#  - EINRÜCKUNG (Leerzeichen am Zeilenanfang) ist in Python wichtig! Sie
#    zeigt, welche Zeilen zusammengehören, z.B. was in einer Schleife oder
#    in einer Funktion steht.
#  - Mit "=" speichert man einen Wert in einer VARIABLEN, z.B. bedeutet
#    "SPEED = 0.25", dass in dem Namen SPEED jetzt die Zahl 0.25 steckt.
#
#  Wichtig: So funktioniert das Koordinatensystem im Fenster
#  ---------------------------------------------------------
#  Der Punkt (0, 0) ist OBEN LINKS im Fenster (nicht unten links wie in Mathe!).
#
#      (0,0) ----------> x wird größer (nach rechts)
#        |
#        |
#        v
#      y wird größer (nach UNTEN)
#
#  Wenn der Schleim also nach oben springen soll, muss sein y-Wert KLEINER
#  werden. Das ist am Anfang ungewohnt, aber gleich wichtig beim Springen.
# =============================================================================


# "import" lädt eine fertige Programm-Bibliothek. PyGame enthält alles, was
# man für einfache Spiele braucht: Fenster, Bilder, Tastatur, Zeitmessung ...
import pygame


# -----------------------------------------------------------------------------
#  EINSTELLUNGEN
# -----------------------------------------------------------------------------
#  Diese Werte werden im ganzen Programm benutzt. Sie sind in GROSSBUCHSTABEN
#  geschrieben – das ist unter Programmierern ein Zeichen dafür, dass sich
#  der Wert während das Programm läuft nicht ändert (eine "Konstante").
#
#  Tipp zum Ausprobieren: Ändere hier mal ein paar Zahlen (z.B. SPEED,
#  JUMP_SPEED oder GRAVITY) und schau, was passiert!

# --- Angaben zum Bild mit den Animationsphasen (dem "Spritesheet") ---
#
#  Ein Spritesheet ist EIN Bild, in dem viele kleine Einzelbilder (Phasen)
#  wie in einem Raster nebeneinander und untereinander liegen. Spielt man
#  sie schnell hintereinander ab, sieht es aus wie ein Film – genau wie bei
#  einem Daumenkino.
#
#  Unser Bild sieht ungefähr so aus (jedes Kästchen ist 32 x 32 Pixel groß):
#
#      +---+---+---+---+
#      | 0 | 1 | 2 | 3 |     <- Reihe 0
#      +---+---+---+---+
#      | 4 | 5 | 6 | 7 |     <- Reihe 1
#      +---+---+---+---+
#      | 8 |   |   |   |     <- Reihe 2 (nur ein Kästchen benutzt)
#      +---+---+---+---+

FRAME_W, FRAME_H = 32, 32   # Breite und Höhe EINER Phase in Pixeln
                            # (zwei Variablen auf einmal: FRAME_W = 32, FRAME_H = 32)
COLUMNS = 4                 # So viele Phasen liegen in einer Reihe nebeneinander
FRAME_COUNT = 9             # So viele Phasen gibt es insgesamt (0 bis 8)
SCALE = 4                   # Vergrößerung: 32 Pixel sind sehr klein, deshalb
                            # zeigen wir alles 4-mal so groß an (128 x 128 Pixel)
FRAME_DURATION = 100        # So lange (in Millisekunden) wird jede Phase gezeigt.
                            # 1000 ms = 1 Sekunde, also 10 Phasen pro Sekunde.

# --- Angaben zum Fenster und zur Bewegung ---
SCREEN_W, SCREEN_H = 640, 400   # Größe des Fensters: 640 Pixel breit, 400 hoch
GROUND_Y = 340              # Hier (y = 340) ist der Boden. Darauf steht der Schleim.
SPEED = 0.25                # Laufgeschwindigkeit: 0,25 Pixel pro Millisekunde
                            # (das sind 250 Pixel pro Sekunde)
JUMP_SPEED = 0.9            # Wie schnell der Schleim beim Absprung nach oben fliegt
GRAVITY = 0.0025            # Schwerkraft: So stark wird der Schleim in jeder
                            # Millisekunde nach unten gezogen


# -----------------------------------------------------------------------------
#  FUNKTION: Einzelbilder aus dem Spritesheet ausschneiden
# -----------------------------------------------------------------------------
#  Eine FUNKTION ist ein kleines Unterprogramm mit einem Namen. Man schreibt
#  sie einmal mit "def" und kann sie danach beliebig oft benutzen ("aufrufen").
#  In den Klammern stehen die PARAMETER – also die Angaben, die die Funktion
#  zum Arbeiten braucht. "scale=1" heißt: Gibt man nichts an, ist scale = 1.
#
#  Diese Funktion bekommt den Dateinamen des Spritesheets und liefert eine
#  LISTE mit allen Einzelbildern zurück.

def load_frames(path, frame_w, frame_h, columns, count, scale=1):
    """Schneidet die einzelnen Phasen aus dem Spritesheet aus."""

    # Das ganze Bild aus der Datei laden.
    # convert_alpha() sorgt dafür, dass durchsichtige Stellen im Bild auch
    # im Spiel durchsichtig bleiben (und das Zeichnen schneller geht).
    sheet = pygame.image.load(path).convert_alpha()

    # Eine leere Liste. Hier sammeln wir gleich alle Einzelbilder.
    # Eine Liste ist wie eine nummerierte Schublade: frames[0], frames[1], ...
    frames = []

    # Eine FOR-SCHLEIFE wiederholt den eingerückten Block mehrmals.
    # range(count) zählt von 0 bis count-1, hier also i = 0, 1, 2, ..., 8.
    for i in range(count):

        # Jetzt rechnen wir aus, WO das Kästchen Nummer i im Bild liegt.
        #
        #   i % columns   ->  "%" ist der REST beim Teilen (Modulo).
        #                     Er sagt uns die SPALTE.  Beispiel: 6 % 4 = 2
        #   i // columns  ->  "//" ist Teilen OHNE Komma (ganzzahlig).
        #                     Das ergibt die REIHE.    Beispiel: 6 // 4 = 1
        #
        #   Kästchen 6 liegt also in Spalte 2, Reihe 1. (Schau oben ins Raster!)
        #   Multipliziert mit 32 Pixeln bekommen wir die Pixel-Position.
        x = (i % columns) * frame_w
        y = (i // columns) * frame_h

        # Ein Rechteck an dieser Stelle aus dem großen Bild "ausschneiden".
        # (Genauer: subsurface schaut nur auf diesen Teil des Bildes.)
        frame = sheet.subsurface(pygame.Rect(x, y, frame_w, frame_h))

        # Wenn eine Vergrößerung gewünscht ist, das Einzelbild vergrößern.
        # "!=" bedeutet "ungleich".
        if scale != 1:
            frame = pygame.transform.scale(frame, (frame_w * scale, frame_h * scale))

        # Das fertige Einzelbild hinten an die Liste anhängen.
        frames.append(frame)

    # Die fertige Liste an denjenigen zurückgeben, der die Funktion aufgerufen hat.
    return frames


# -----------------------------------------------------------------------------
#  KLASSE: AnimatedSprite – eine Figur, die sich animiert
# -----------------------------------------------------------------------------
#  Eine KLASSE ist wie ein Bauplan. Aus dem Bauplan kann man später echte
#  Dinge bauen – die nennt man OBJEKTE. (Bauplan "Auto" -> viele echte Autos.)
#
#  Ein "Sprite" ist in Spielen eine Figur oder ein Gegenstand, der auf dem
#  Bildschirm gezeichnet wird. PyGame hat schon eine fertige Klasse
#  pygame.sprite.Sprite. Wir schreiben sie in Klammern dahinter und sagen
#  damit: "Unser AnimatedSprite ist ein Sprite und kann alles, was ein Sprite
#  kann – plus ein bisschen mehr." Das nennt man VERERBUNG.
#
#  Das Wort "self" bedeutet "ich selbst" – also das Objekt, um das es gerade
#  geht. self.index ist z.B. "meine eigene Bildnummer". So hat jede Figur
#  ihre eigenen Werte.

class AnimatedSprite(pygame.sprite.Sprite):

    # __init__ wird automatisch ausgeführt, wenn ein neues Objekt gebaut wird.
    # Hier legen wir die Startwerte fest.
    def __init__(self, frames, pos, frame_duration):
        # Zuerst den Bauplan der "Eltern-Klasse" (pygame.sprite.Sprite)
        # ausführen, damit PyGame die Figur richtig vorbereitet.
        super().__init__()

        # Die Einzelbilder, so wie sie im Spritesheet sind (Blick nach rechts).
        self.frames_right = frames

        # Gespiegelte Phasen für die Bewegung nach links.
        # flip(f, True, False) spiegelt ein Bild waagerecht (True) aber nicht
        # senkrecht (False). Die Schreibweise [ ... for f in frames ] baut
        # eine neue Liste: Für JEDES Bild f in frames kommt eine gespiegelte
        # Version hinein. So sparen wir uns ein zweites Spritesheet.
        self.frames_left = [pygame.transform.flip(f, True, False) for f in frames]

        self.frame_duration = frame_duration  # Wie lange eine Phase gezeigt wird
        self.index = 0             # Welche Phase gerade gezeigt wird (Start: 0)
        self.elapsed = 0           # Wie viel Zeit seit dem letzten Bildwechsel vergangen ist
        self.facing_left = False   # Schaut die Figur nach links? Am Anfang: nein.
                                   # (True = wahr/ja, False = falsch/nein)

        # PyGame erwartet bei jedem Sprite zwei Dinge:
        #   self.image -> WAS gezeichnet wird (das aktuelle Bild)
        #   self.rect  -> WO es gezeichnet wird (ein Rechteck mit Position und Größe)
        self.image = self.frames_right[0]
        # midbottom=pos heißt: Die Mitte der UNTERKANTE des Bildes soll an der
        # Position pos liegen. So "steht" die Figur mit den Füßen auf dem Punkt.
        self.rect = self.image.get_rect(midbottom=pos)

    # update() wird in jedem Durchlauf des Spiels einmal aufgerufen.
    # dt ("delta time") ist die Zeit in Millisekunden seit dem letzten Mal.
    def update(self, dt):
        # Zeitbasiert weiterschalten, unabhängig von der Bildrate.
        #
        # Warum so umständlich? Manche Computer sind schneller als andere.
        # Würden wir einfach bei JEDEM Durchlauf ein Bild weiterschalten,
        # würde der Schleim auf schnellen Computern hektisch zappeln.
        # Stattdessen zählen wir die vergangene Zeit zusammen ("+=" heißt
        # "dazuzählen") und schalten erst weiter, wenn genug Zeit um ist.
        self.elapsed += dt

        # Eine WHILE-SCHLEIFE wiederholt sich, SOLANGE die Bedingung stimmt.
        # Falls sehr viel Zeit vergangen ist, wird auch mehrmals weitergeschaltet.
        while self.elapsed >= self.frame_duration:
            self.elapsed -= self.frame_duration    # die "verbrauchte" Zeit abziehen
            # Eine Phase weiter. Das "% len(...)" sorgt dafür, dass nach der
            # letzten Phase (8) wieder bei 0 angefangen wird:
            #   7 -> 8 -> 0 -> 1 -> 2 ...   (len() gibt die Länge der Liste an, hier 9)
            self.index = (self.index + 1) % len(self.frames_right)

        # Je nach Blickrichtung die passende Bilder-Liste auswählen.
        # Lies es so: "frames ist frames_left, WENN facing_left wahr ist,
        # SONST frames_right."
        frames = self.frames_left if self.facing_left else self.frames_right

        # Das Bild mit der aktuellen Nummer wird jetzt angezeigt.
        self.image = frames[self.index]


# -----------------------------------------------------------------------------
#  KLASSE: Slime – unsere Spielfigur
# -----------------------------------------------------------------------------
#  Der Slime ERBT von AnimatedSprite. Er kann also schon alles mit der
#  Animation. Dazu bekommt er jetzt noch: Steuerung mit der Tastatur,
#  Springen und Schwerkraft.

class Slime(AnimatedSprite):

    def __init__(self, frames, pos, frame_duration):
        # Erst alles vom Bauplan AnimatedSprite einrichten lassen ...
        super().__init__(frames, pos, frame_duration)

        # ... dann die eigenen Werte dazu.
        #
        # Position als Kommazahl (float), damit auch kleine Schritte pro
        # Frame wirken. Das Rechteck (rect) kann nur ganze Pixel speichern.
        # Bewegt sich der Schleim nur 0,25 Pixel weit, würde das beim Runden
        # immer wieder verloren gehen und er würde gar nicht vom Fleck kommen.
        # Deshalb merken wir uns die genaue Position extra in x und y.
        self.x = float(self.rect.centerx)   # Mitte des Schleims (waagerecht)
        self.y = float(self.rect.bottom)    # Unterkante des Schleims (seine "Füße")

        self.vy = 0.0          # Geschwindigkeit nach oben/unten ("v" wie velocity,
                               # also Geschwindigkeit, "y" für die senkrechte Richtung)
        self.on_ground = True  # Steht der Schleim gerade auf dem Boden?

    def update(self, dt):
        # Nachschauen, welche Tasten gerade gedrückt sind.
        # keys ist danach eine Art Liste: keys[Taste] ist True (1), wenn die
        # Taste gedrückt ist, sonst False (0).
        keys = pygame.key.get_pressed()

        # --- Links / rechts laufen ---
        #
        # Ein kleiner Trick: True zählt wie 1, False wie 0. Daher gilt:
        #   nur RECHTS gedrückt:  1 - 0 =  1  -> nach rechts
        #   nur LINKS gedrückt:   0 - 1 = -1  -> nach links
        #   keine (oder beide):   0 - 0 =  0  -> stehen bleiben
        direction = keys[pygame.K_RIGHT] - keys[pygame.K_LEFT]

        # "if direction:" ist wahr, wenn direction NICHT 0 ist – also wenn
        # sich der Schleim bewegt. Nur dann ändern wir die Blickrichtung.
        # So schaut er nach dem Loslassen weiter in die letzte Richtung.
        if direction:
            self.facing_left = direction < 0   # True, wenn direction negativ ist

        # Neue Position = alte Position + Richtung * Geschwindigkeit * Zeit
        # (wie in Physik: Weg = Geschwindigkeit * Zeit)
        self.x += direction * SPEED * dt

        # --- Springen nur vom Boden aus ---
        # "and" heißt: BEIDE Bedingungen müssen stimmen. Ohne die zweite
        # Bedingung könnte der Schleim in der Luft immer weiter springen.
        if keys[pygame.K_UP] and self.on_ground:
            # Negative Geschwindigkeit = nach OBEN (siehe Koordinatensystem oben!)
            self.vy = -JUMP_SPEED
            self.on_ground = False

        # --- Schwerkraft ---
        # Die Schwerkraft zieht immer nach unten, also wird vy bei jedem
        # Durchlauf ein bisschen größer. Dadurch wird der Sprung erst immer
        # langsamer, bleibt kurz oben stehen und dann fällt der Schleim immer
        # schneller wieder herunter – genau wie ein echter Ball.
        self.vy += GRAVITY * dt
        self.y += self.vy * dt

        # Ist der Schleim unter den Boden gerutscht? Dann zurück auf den
        # Boden setzen und die Fallbewegung stoppen.
        if self.y >= GROUND_Y:
            self.y = GROUND_Y
            self.vy = 0.0
            self.on_ground = True

        # --- Am Bildschirmrand stoppen ---
        # x ist die MITTE des Schleims. Damit er nicht halb aus dem Fenster
        # verschwindet, darf x nicht kleiner als die halbe Breite sein und
        # nicht größer als (Fensterbreite - halbe Breite).
        #   min(a, b) gibt die kleinere Zahl zurück,
        #   max(a, b) gibt die größere Zahl zurück.
        # Zusammen "klemmen" sie x zwischen linken und rechten Rand ein.
        half_w = self.rect.width / 2
        self.x = max(half_w, min(SCREEN_W - half_w, self.x))

        # Jetzt noch die Animation weiterschalten. Das erledigt die
        # update()-Funktion aus dem Bauplan AnimatedSprite für uns.
        super().update(dt)

        # Zum Schluss das Rechteck (für das Zeichnen) an die neue Position
        # setzen. round() rundet auf ganze Pixel.
        self.rect.midbottom = (round(self.x), round(self.y))



# =============================================================================
#  HAUPTPROGRAMM – hier geht es wirklich los!
# =============================================================================
#  Alles oben waren nur Einstellungen und Baupläne. Erst ab hier wird
#  tatsächlich etwas ausgeführt.

# PyGame starten.
pygame.init()

# Das Fenster öffnen (640 x 400 Pixel) und ihm einen Titel geben.
screen = pygame.display.set_mode((SCREEN_W, SCREEN_H))
pygame.display.set_caption("Slime-Animation")

# Eine Uhr, mit der wir die Geschwindigkeit des Spiels steuern und die
# vergangene Zeit messen können.
clock = pygame.time.Clock()

# Die Einzelbilder aus dem Spritesheet laden (unsere Funktion von oben).
frames = load_frames("images/Enemy1_slime.png",
                     FRAME_W, FRAME_H, COLUMNS, FRAME_COUNT, SCALE)

# Aus dem Bauplan "Slime" einen echten Schleim bauen. Er startet in der
# Mitte des Fensters (SCREEN_W // 2 = 320) direkt auf dem Boden.
slime = Slime(frames, (SCREEN_W // 2, GROUND_Y), FRAME_DURATION)

# Eine Gruppe von Sprites. Hier ist zwar nur der Schleim drin, aber mit einer
# Gruppe kann man später ganz einfach weitere Figuren hinzufügen und alle
# auf einmal bewegen und zeichnen.
sprites = pygame.sprite.Group(slime)


# -----------------------------------------------------------------------------
#  DIE SPIELSCHLEIFE ("Game Loop")
# -----------------------------------------------------------------------------
#  Jedes Spiel besteht im Kern aus einer Schleife, die immer wieder dieselben
#  drei Schritte macht – viele Male pro Sekunde:
#
#      1. Eingaben prüfen   (Wurde das Fenster geschlossen? Tasten gedrückt?)
#      2. Alles bewegen     (update)
#      3. Alles zeichnen    (draw)
#
#  Das Ganze läuft so lange, bis "running" auf False gesetzt wird.

running = True
while running:
    # clock.tick(60) wartet kurz, sodass die Schleife höchstens 60-mal pro
    # Sekunde läuft (60 "Bilder pro Sekunde", engl. FPS). Außerdem gibt sie
    # zurück, wie viele Millisekunden seit dem letzten Durchlauf vergangen
    # sind – bei 60 FPS sind das etwa 16 ms.
    dt = clock.tick(60)  # vergangene Millisekunden seit dem letzten Frame

    # --- 1. Eingaben prüfen ---
    # PyGame sammelt alle "Ereignisse" (Events), z.B. Mausklicks oder das
    # Schließen des Fensters. Wir gehen sie hier einzeln durch.
    for event in pygame.event.get():
        # "==" prüft, ob zwei Dinge gleich sind (nicht verwechseln mit "=").
        # QUIT bedeutet: Jemand hat auf das "X" des Fensters geklickt.
        if event.type == pygame.QUIT:
            running = False   # -> Die while-Schleife endet nach diesem Durchlauf.

    # --- 2. Alles bewegen ---
    # Ruft für jede Figur in der Gruppe deren update()-Funktion auf.
    # (Beim Schleim also: Tasten auswerten, bewegen, Animation weiterschalten.)
    sprites.update(dt)

    # --- 3. Alles zeichnen ---
    # Zuerst das ganze Fenster mit einer Farbe füllen. Das löscht das alte
    # Bild – sonst würde der Schleim eine Spur hinter sich herziehen.
    # Farben werden als (Rot, Grün, Blau) angegeben, jeweils von 0 bis 255.
    # (40, 40, 160) ist ein dunkles Blau – unser Himmel.
    screen.fill((40, 40, 160))

    # Den Boden als braunes Rechteck zeichnen.
    # Angabe: (x, y, Breite, Höhe) -> ab dem Boden (y = 340) bis ganz unten.
    pygame.draw.rect(screen, (70, 50, 40),
                     (0, GROUND_Y, SCREEN_W, SCREEN_H - GROUND_Y))

    # Alle Figuren der Gruppe zeichnen (jeweils self.image an der Stelle self.rect).
    sprites.draw(screen)

    # Bisher wurde alles nur "im Hintergrund" gemalt. flip() zeigt das fertige
    # Bild jetzt auf einen Schlag im Fenster an. So flackert nichts.
    pygame.display.flip()

# Die Schleife ist zu Ende (Fenster wurde geschlossen) -> PyGame sauber beenden.
pygame.quit()

Ein paar Hinweise dazu:

  • Das Fenster kann mit Klick auf ‘X’ geschlossen werden, oder mit Alt+F4
  • Das besprochene Double-Buffering wird in der Zeile pygame.display.flip() gemacht.
  • super() verweist auf die geerbte und überladene Funktion der Basisklasse (“…führe die Originalfunktion aus…”)
  • Die Animation ist derzeit rein zeitabhängig und nicht mit der Sprunghöhe synchronisiert. Im richtigen Spiel sollte der Schleim beim hochspringen zur Kugel werden und in den letzten Millisekunden vor dem Aufprall erst wieder platt gedrückt sein. Dies erfordert eine Auswahl des anzuzeigenden Frames in Abhängigkeit von der Bewegungshöhe des Schleims. Konkret muss die Zeile super().update(dt) durch was Schleimspezifisches ersetzt werden. Die Basisklassenfunktionalität kann man aber so für andere, rein zeitabhängige Sprites (brennende Fackeln, fließende Springbrunnen, Bäume etc.) weiterhin so nutzen.

Nächste Schritte

Scrollender Hintergrund und 2-Ebenen-Parallax-Scrolling und natürlich richtige Physik (viele Schleimmonster hüpfen dem Spieler entgegen, fallen von Plattformen auf darunterliegende herunten und so weiter …)

This post is licensed under CC BY 4.0 by the author.