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.
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.
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.
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.
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.
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!
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. ↩
»Wenn es schwimmt wie eine Ente, quakt wie eine Ente und watschelt wie eine Ente, dann ist es eine Ente.« ↩
Ü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!