image image


image

The Nature of Code in Pygame Zero, Kapitel 1 (reloaded)

Ich habe die Programmbeispiele 1.7 bis 1.111 aus Daniel Shiffmans Buch »The Nature of Code« noch einmal neu von Processing (Java) nach Pygame Zero portiert, nachdem ich die graphischen Primitive in Pygame Zero neu bewertet habe. Ich habe nämlich herausgefunden, daß man einen Kreis mit einem Rand versehen kann, in dem man einfach über einen filled_circle einen gleichgroßen circle in der gewünschten Randfarbe zeichnet. Und schon wirken die Kreise auch in Pygame Zero nicht mehr so ausgefranzt. Und das Ergebnis kommt dann den Sketchen in Shiffmans Buch doch viel näher als meine ursprünglichen Beispiele mit den Fußbällen.

Beispiel 1.7: Einfache Bewegung

Im ersten Beispiel wird die Klasse Mover implementiert, die in diesem Stadium nur die Vektoren location und velocity besitzt. Sie sieht dann so aus:

class Mover(object):
    
    def __init__(self, x, y, r):
        self.location = PVector(x, y)
        self.velocity = PVector(random.uniform(-5, 5), random.uniform(-5, 5))
        self.radius = r
    
    def display(self):
        screen.draw.filled_circle((self.location.x, self.location.y), self.radius, (255, 0, 0))
        screen.draw.circle((self.location.x, self.location.y), self.radius, (0, 0, 0))
    
    def update(self):
        self.location.add(self.velocity)
    
    def check_edges(self):
        if (self.location.x > WIDTH - RADIUS):
            self.location.x = WIDTH - RADIUS
            self.velocity.x *= -1
        elif (self.location.x < RADIUS):
            self.location.x = RADIUS
            self.velocity.x *= -1
        if (self.location.y > HEIGHT - RADIUS):
            self.location.y = HEIGHT - RADIUS
            self.velocity.y *= -1
        elif (self.location.y < RADIUS):
            self.location.y = RADIUS
            self.velocity.y *= -1

Die Ränderbehandlung wird in diesem und den folgenden Beispielen anders behandelt als bei Shiffman, ich lasse den Mover an den Rändern abprallen, statt ihn an der gegenüberliegenden Seite wieder auftauchen zu lassen. Dieses Bouncing Ball-Verhalten gefiel mir einfach besser.

Ein Mover-Objekt wird mit

mover = Mover(200, 200, RADIUS)

initialisiert, wobei RADIUS eine Konstante ist.

Pygame Zero erwartet zwei Funktionen, nämlich draw() und update() und die sehen bei mir wie folgt aus:

def draw():
    screen.fill((149, 224, 245))
    mover.display()

def update():
    mover.update()
    mover.check_edges()

Zusätzlich in allen meinen Pygame Zero Skripten habe ich die Methode on_key_down() eingebaut,

def on_key_down():
    ## Spielende mit ESC
    if keyboard.escape:
        sys.exit()

die dafür sorgt, daß man ein Skript nicht nur mit der Maus im Schließknopf des Fensters, sondern auch über die Tastatur mit Escape beenden kann.

Das vollständige Skript findet Ihr in meinem GitHub-Repositorium.

Beispiel 1.8: Bewegung und konstante Beschleunigung

In diesem Beispiel wird der Bewegungsvektor durch einen (konstanten) Beschleunigungsvektor verändert. In der Klasse Mover verändert sich dadurch der Konstruktor

    def __init__(self, x, y, r):
        self.location = PVector(x, y)
        self.velocity = PVector(0, 0)
        self.acceleration = PVector(-0.001, 0.1)
        self.radius = r
        self.topspeed = 20

und die update()-Methode:

    def update(self):
        self.velocity.add(self.acceleration)
        self.velocity.limit(self.topspeed)
        self.location.add(self.velocity)

Die Startposition der mover-Instanz habe ich dieses Mal in die Mitte nach oben gelegt:

mover = Mover(200, 20, RADIUS)

Der Rest ist eigentlich gleich. Auch dieses Skript findet Ihr in meinem Repositorium.

Beispiel 1.9: Bewegung und zufällige Beschleunigung:

Das letzte Beispiel ist etwas langweilig. Mehr Abwechslung kommt herein, wenn man die Beschleunigung bei jedem Durchlauf zufällig verändert. Dafür muß der acceleration-Vektor nicht mehr im Konstruktor, sondern bei jedem Durchlauf in der update()-Methode einen neuen, zufälligen Wert zugewiesen bekommen:

    def update(self):
        self.acceleration = PVector.random2D()
        # self.acceleration.mult(0.5)
        self.acceleration.mult(random.uniform(0.0, 2.0))
        self.velocity.add(self.acceleration)
        self.velocity.limit(self.topspeed)
        self.location.add(self.velocity)

Wahlweise kann man die Zeile

        self.acceleration.mult(random.uniform(0.0, 2.0))

durch die oben auskommentierte Zeile

        self.acceleration.mult(0.5)

ersetzen und erzeugt so ein anderes Verhalten des Movers.

Außer, da ich das mover-Objekt nun wieder in der Mitte des Fensters initialisiert habe, hat sich sonst nichts verändert. Natürlich gibt es auch dieses Skript in meinem GitHub-Repositorium.

Beispiel 1.10: Der Mover folgt der Maus

Da Pygame Zero keine Methode anbietet, die Mausposition abzufragen, mußte ich für dieses Beispiel zusätzlich Pygame importieren. Dann ist aber alles straightforward und die Änderungen sind wieder nur in der update()-Methode der Klasse Mover zu finden:

    def update(self):
        mouse_x, mouse_y = pygame.mouse.get_pos()
        mouse = PVector(mouse_x, mouse_y)
        dir = mouse - self.location
        dir.normalize()
        dir.mult(0.5)
        self.acceleration = dir
        self.velocity.add(self.acceleration)
        self.velocity.limit(self.topspeed)
        self.location.add(self.velocity)

Eine Besonderheit ist, daß ich die Substraktion (wie auch die Addition) zweier Vektoren in meiner Implementierung der PVector-Klasse durch Operatoren-Überladung realisiert habe, daher kann ich zwei Vektoren – wie oben – mit

        dir = mouse - self.location

voneinander abziehen. Daß die Variable dir dann ebenfalls ein Vektor sein muß, erkennt Python durch Duck Typing.2

Eigentlich ist es unnötig zu erwähnen, daß es dieses Skript ebenfalls in meinem GitHub-Repositorium gibt, aber irgendwo muß ich den Link darauf ja unterbringen.

Beispiel 1.11:

Als krönenden Abschluß des ersten Kapitels läßt Daniel Shiffman mehrere Mover der Maus folgen. Auch ich bin ihm gefolgt, weiche aber ein wenig von seinem Beispiel ab, da ich die Mover in unterschiedlichen, zufälligen Größen implementiert habe und außerdem habe ich als Reminiszenz an Daniel Shiffman meine Coding Train Farbpalette verwendet. Da das Programm so substanziell erweitert wurde, hier der vollständige Code:

# Example 1.11: Motion 101 (Acceleration towards Mouse)
# aus »The Nature of Code« portiert nach Pygame Zero
# 14. Juni 2020 by Jörg Kantel
import pgzrun
import pygame
from pvector import PVector
from random import randint, choice
import sys

WIDTH = 400
HEIGHT = 400
TITLE = "Motion 101: Many Movers Acceleration Towards Mouse"
NUMBERMOVERS = 10

colorlist = [(239, 242, 63), (198, 102, 230), (151, 87, 165), (129, 122, 198), (98, 199, 119)]

class Mover(object):
    
    def __init__(self):
        self.location = PVector(randint(0, HEIGHT), randint(0, WIDTH))
        self.velocity = PVector(0, 0)
        self.topspeed = randint(6, 12)
        self.radius = randint(8, 24)
        self.color = choice(colorlist)
    
    def display(self):
        screen.draw.filled_circle((self.location.x, self.location.y), self.radius, self.color)
        screen.draw.circle((self.location.x, self.location.y), self.radius, (0, 0, 0))
    
    def update(self):
        mouse_x, mouse_y = pygame.mouse.get_pos()
        mouse = PVector(mouse_x, mouse_y)
        dir = mouse - self.location
        dir.normalize()
        dir.mult(0.5)
        self.acceleration = dir
        self.velocity.add(self.acceleration)
        self.velocity.limit(self.topspeed)
        self.location.add(self.velocity)
    
    def check_edges(self):
        if (self.location.x > WIDTH - self.radius):
            self.location.x = WIDTH - self.radius
            self.velocity.x *= -1
        elif (self.location.x < self.radius):
            self.location.x = self.radius
            self.velocity.x *= -1
        if (self.location.y > HEIGHT - self.radius):
            self.location.y = HEIGHT - self.radius
            self.velocity.y *= -1
        elif (self.location.y < self.radius):
            self.location.y = self.radius
            self.velocity.y *= -1
    

movers = []
for _ in range(NUMBERMOVERS):
    movers.append(Mover())

def draw():
    screen.fill((149, 224, 245))
    for mover in movers:
        mover.display()

def update():
    for mover in movers:
        mover.update()
        mover.check_edges()
    
def on_key_down():
    ## Spielende mit ESC
    if keyboard.escape:
        sys.exit()

pgzrun.go()

Nach all dem vorher Gesagtem sollte der Code (der natürlich auch auf GitHub zu finden ist) selbsterklärend sein. Im Konstruktor bekommen die einzelnen Mover eine zufällige Höchstgeschwindigkeit, einen zufälligen Durchmesser und (mit choice()) eine zufällige Farbe aus der Liste der Coding Train-Farben zugewiesen. Die mover kommen in die Liste movers, über die in draw() und update() jeweils iteriert wird. Das ist alles!

Damit ist die Portierung von Kapitel 1 abgeschlossen. Ich habe bei diesem Refaktoring noch einmal sehr viel über Pygame Zero gelernt und fühle mich nun für eine Portierung des zweiten Kapitels von The Nature of Code besser gewappnet. Still digging!

image

  1. Die Programmbeispiele 1.1 bis 1.6 in »The Nature of Code« zeigen die Implementierung der PVector-Klasse. Eventuell nehme ich das zum Anlaß, meine eigene (Python-) PVector-Klasse neu vorzustellen. Sie hat seit der letzten Vorstellung nämlich noch einmal einige Erweiterungen erfahren. 

  2. »Wenn es schwimmt wie eine Ente, quakt wie eine Ente und watschelt wie eine Ente, dann ist es eine Ente.« 


(Kommentieren) 

image image



Über …

Der Schockwellenreiter ist seit dem 24. April 2000 das Weblog digitale Kritzelheft von Jörg Kantel (Neuköllner, EDV-Leiter Rentner, Autor, Netzaktivist und Hundesportler — Reihenfolge rein zufällig). Hier steht, was mir gefällt. Wem es nicht gefällt, der braucht ja nicht mitzulesen. Wer aber mitliest, ist herzlich willkommen und eingeladen, mitzudiskutieren!

Alle eigenen Inhalte des Schockwellenreiters stehen unter einer Creative-Commons-Lizenz, jedoch können fremde Inhalte (speziell Videos, Photos und sonstige Bilder) unter einer anderen Lizenz stehen.

Der Besuch dieser Webseite wird aktuell von der Piwik Webanalyse erfaßt. Hier können Sie der Erfassung widersprechen.

Diese Seite verwendet keine Cookies. Warum auch? Was allerdings die iframes von Amazon, YouTube und Co. machen, entzieht sich meiner Kenntnis.


Werbung

Diese Spalte wurde absichtlich leergelassen!


Werbung


image  image  image
image  image  image


image