Wo ist unser Vektor, Viktor? Nachschlag zu Teil 2

Spieleprogrammierung
Pygame
Python
Nature of Code
Autor:in

Jörg Kantel

Veröffentlichungsdatum

18. Februar 2025

Zu meinem gestrigen Versuch, ein von der Hühnerhauswand abprallendes Küken mithilfe von Pygame (CE) und Pygames Vektorklasse zu programmieren, hatte der Gravitar in meinen Kommentaren noch ein paar äußerst hilfreiche Anmerkungen, die ich stante pede in meinem Programm (bouncingball_vectors.py) umgesetzt habe:

So sei der separate import von pygame.math nicht mehr notwendig, weil Pygame die Bibliothek gleich mitimportiere. Außerdem könne man den Aufruf der Methode noch mehr verkürzen, wenn man zum Beispiel zu Beginn des Programms

vec2 = pygame.Vector2

vereinbare. Denn da könne man die initiale Zuweisung mit

self.position = vec2(100, 100)
self.velocity = vec2(2.5, 2)

erledigen. Außerdem könne man bei self.rect.topleft(oder center, bottom, midbottom etc.) eine Zuweisung mit einem Vektor vornehmen, zum Beispiel:

self.rect.topleft = self.position
...
self.rect.topleft += self.velocity

Und last but not least wies er darauf hin, daß ich mir mit self.rect.right (oder left oder top oder bottom) die Addition (oder Subtraktion) des Radius sparen könne:

if self.rect.right > WIDTH  or self.rect.left < 0:
    self.velocity.x *= -1
if self.rect.bottom > HEIGHT or self.rect.top < 0:
    self.velocity.y *= -1

Der Vollständigkeit halber hier noch einmal das gesamte Programm mit den vorgeschlagenen Änderungen:

# Bouncing Chicken with Vectors
import asyncio
import pygame
import os, sys

# Hier wird der Pfad zum Verzeichnis der Assets gesetzt
DATAPATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data")

# Einige nützliche Konstanten
WIDTH = 800
HEIGHT = 450
CHICKEN_SIZE = 48
TITLE = "Bouncing Chicken with Vectors (Pygame Version)"
FPS = 60  # Framerate

# Farben
BG_COLOR = 59, 122, 87, 255  # Billardtisch-Grün

vec2 = pygame.Vector2

# Klassen
# ---------------------------------------------------------------------- #
## Class GameWorld
class GameWorld:

    def __init__(self):
        # Pygame und das Fenster initialisieren
        pygame.init()
        self.screen = pygame.display.set_mode((WIDTH, HEIGHT))
        pygame.display.set_caption(TITLE)

        self.clock = pygame.time.Clock()
        self.keep_going = True

    def reset(self):
        # Neustart oder Status zurücksetzen
        # Hier werden alle Elemente der GameWorld initialisiert
        self.all_sprites = pygame.sprite.Group()
        # Load Assets
        self.chicken_im = pygame.image.load(os.path.join(DATAPATH, "chick.png")).convert_alpha()
        self.chicken_im = pygame.transform.scale(self.chicken_im, (CHICKEN_SIZE, CHICKEN_SIZE))
        chicken = Chicken(self)
        self.all_sprites.add(chicken)

    def events(self):
        # Poll for events
        for event in pygame.event.get():
            if ((event.type == pygame.QUIT) or
                (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE)):
                self.keep_going = False

    def update(self):
        self.all_sprites.update()

    def draw(self):
        self.screen.fill(BG_COLOR)
        # Game drawings go here
        self.all_sprites.draw(self.screen)
        # Alle Änderungen auf den Bildschirm
        pygame.display.flip()

# ---------------------------------------------------------------------- #
## Class Chicken
class Chicken(pygame.sprite.Sprite):

    def __init__(self, _world):
        super().__init__()
        self.game_world = _world
        self.image = self.game_world.chicken_im
        self.rect = self.image.get_rect()
        self.position = vec2(100, 100)
        self.rect.topleft = self.position
        self.velocity = vec2(2.5, 2)

    def update(self):
        self.rect.topleft += self.velocity
        if self.rect.right > WIDTH  or self.rect.left < 0:
            self.velocity.x *= -1
        if self.rect.bottom > HEIGHT or self.rect.top < 0:
            self.velocity.y *= -1

# ---------------------------------------------------------------------- #
# ## Hauptprogramm
world = GameWorld()
world.reset()

# Hauptschleife
async def main():
    while world.keep_going:
        # Framerate festsetzen
        world.clock.tick(FPS)

        world.events()
        world.update()
        world.draw()
        await asyncio.sleep(0)  # Very important, and keep it 0

    pygame.quit()
    sys.exit(0)

asyncio.run(main())

Jetzt habe ich aber Blut geleckt. Ich werde zumindest die Beispiele aus dem Vektoren-Kapitel (Chapter 1) und aus dem Kapitel über Kraft (Chapter 2) aus Daniel Shiffmans »The Nature of Code« nach Pygame portieren und um einige eigene Beispiele ergänzen. Und dann lacht mich das Kapitel über Physik-Bibliotheken (Chapter 6) an. Was spricht dagegen, Matter.js durch Pymunk zu ersetzen? Zumal ich schon lange mal etwas mit dieser Python-Physik-Engine anstellen wollte.

Ich habe in den nächsten Tagen (oder Wochen… oder Monaten… ?) also wieder einiges zu tun und ich freue mich darauf. Still digging!


Bild: Bouncing Chicken, erstellt mit Scenario. Prompt: »A yellow chick flutters against a green painted wall in the henhouse«. Modell: Flux Composition, Style: Expressive Comic Characters.