@Atalanttore: in Countdown.update: Du solltest den Text auch aktualisieren. remaining_time und _obsolet sollten keine Attribute sein, das sind ganz normale lokale Variablen. Das Argument remaining_time, das in __init__ übergeben wird, wird gar nicht benutzt, START_COUNTDOWN ist fix. Das ganze if- und return kann man kurz als `return remaining_time>0` schreiben.
Es gibt ein _things_on_screen und ein _things, wobei zweiteres gar nicht benutzt wird.
Geometrische Objekte werden generiert, aber nicht angezeigt.
-
- User
- Beiträge: 407
- Registriert: Freitag 6. August 2010, 17:03
Ohne `scene.clear()` schaffe ich es nicht den Countdown-Text zu aktualisieren. Wie geht das ohne `scene.clear()`?__deets__ hat geschrieben: ↑Dienstag 1. Januar 2019, 11:07 Denn du könntest ein Countdown-Objekt analog zu einem Star einführen. Und dann die _stars in _things oder so umbennennen. Am Anfang erzeugst du dann nur ein Countdown-Objekt. Dessen Update Methode wird dann immer mit der verflossenen Zeit aufgerufen & modifiziert sein Text-item (oder entfernt es und legt ein neues an). Mit scene clear darf dann natürlich nicht gearbeitet werden.
Was meinst du mit "Protokoll"?__deets__ hat geschrieben: ↑Dienstag 1. Januar 2019, 11:07 Wenn der Countdown abgelaufen ist, entfernt man das Objekt aus der Liste der Dinge, und fügt die Sterne ein. Dazu muss man ggf das Protokoll etwas erweitern: Update muss zurück geben, ob das Objekt weiter leben soll, oder entfernt werden muss. Und es muss eine neue Liste von Objekten zurück geben können. Man könnte also ein Tupel (True, []) zurück geben in update.
Meinst du mit "generisch", dass die Methode `_update_all()` sowohl den Countdown als auch die Sterne aktualisieren kann?
Warum sollte die Methode `spawn_stars()` ins `countdown`-Objekt? Das `graphics_window`-Objekt habe ich eigentlich als Ablaufsteuerung angesehen, die Objekte erstellt, zur Liste hinzufügt, laufend aktualisiert, usw. Ist das nicht so?__deets__ hat geschrieben: ↑Dienstag 1. Januar 2019, 23:09 Auf dem Weg ja, die spawn_stars Methode sollte natürlich uns Countdown Objekt. Und üblicherweise sammelt man die zu entfernenden Objekte auf, und macht das dann am Ende in einem Rutsch. Genauso fügt man dann neue erst am Ende wirklich hinzu & erst in nächsten Zyklus werden die gerechnet.
@Sirius3: Deine Empfehlungen habe ich so gut wie mir möglich umgesetzt.
Aktueller Code (zumindest der Countdown funktioniert ):
Code: Alles auswählen
import sys
import time
from random import random
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtWidgets import QApplication, QGraphicsView, QGraphicsScene
from PyQt5.QtGui import QPainterPath
START_COUNTDOWN = 3
UPDATE_INTERVAL = 100
SPEED = 5000
MIN_DISTANCE = 30000
DISTANCE_VARIANCE = 20000
D = 1000
class Countdown:
def __init__(self, scene, remaining_time):
self.scene = scene
self.remaining_time = remaining_time
self.painterpath = QPainterPath()
self._path = scene.addPath(self.painterpath)
def update(self, elapsed_time):
self.scene.clear() # Ohne diese Anweisung wird der Text nicht mehr aktualisiert.
self.remaining_time = (START_COUNTDOWN - elapsed_time)
self.scene.addText(f"In {self.remaining_time} Sekunden gehts los!")
return self.remaining_time > 0
class Star:
def __init__(self, scene):
painterpath = QPainterPath()
painterpath.addRect(-10, -10, 20, 20)
self._x = random() * 20000 - 10000
self._y = random() * 20000 - 10000
self._z = random() * 20000 + MIN_DISTANCE
self._speed = random() * SPEED
self._path = scene.addPath(painterpath)
self.draw_pen()
def draw_pen(self):
self._path.setPen(Qt.red)
self._path.setBrush(Qt.transparent)
def draw_brush(self):
self._path.setBrush(Qt.blue)
def update(self, elapsed):
self._z -= elapsed * self._speed
if self._z < 0:
self._z = random() * DISTANCE_VARIANCE + MIN_DISTANCE
self._path.setX(self._x * self.D / self._z)
self._path.setY(self._y * self.D / self._z)
self._path.setScale(1 - self._z / (MIN_DISTANCE + DISTANCE_VARIANCE))
self._path.setRotation(self._path.rotation() + elapsed * 360)
if random() > .5 == 0:
self.draw_pen()
else:
self.draw_brush()
class GraphicsWindow(QGraphicsView):
def __init__(self, parent=None):
super().__init__(parent)
self._last = time.monotonic()
self.scene = QGraphicsScene(self)
self.setScene(self.scene)
self.scene.setSceneRect(-400, -400, 800, 800)
self.showFullScreen()
self.countdown = Countdown(self.scene, START_COUNTDOWN)
self._things_on_screen = list()
self._things_on_screen.append(self.countdown)
self._timer = QTimer()
self._timer.setTimerType(Qt.PreciseTimer)
self._timer.setInterval(100)
self._timer.timeout.connect(self._update_all)
self._timer.start()
self._start = time.monotonic()
def spawn_star(self):
self._things_on_screen.append(Star(self.scene))
def _update_all(self):
now = time.monotonic()
elapsed = now - self._start
for thing in self._things_on_screen:
if not thing.update(elapsed):
self._things_on_screen.clear() # Liste leeren, wenn Startcountdown abgelaufen.
def keyPressEvent(self, event):
if event.key() == Qt.Key_Escape:
self.close()
def main():
app = QApplication(sys.argv)
graphics_window = GraphicsWindow()
graphics_window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Atalanttore
Wenn du keine Moeglichkeit findest, den Text des Items selbst zu aendern (ich habe da auch keine gefunden), aber clear hilft - dann ist die Loesung doch logisch: du musst halt beim wechseln des Textes das *EINE* Item entfernen, statt *ALLER* Items, und ein neues hinzufuegen.
Mit Protokoll meine ich, wie das Fenster und die unterschiedlichen Objekte miteinander reden. Das die update-Methode einen Rueckgabewert hat, das ist eine Vereinbarung, wie sie miteinander kommunizieren, mit bestimmten Erwartungen etc. Also ein Protokoll.
Und ja, generisch heisst, dass das GraphicsWindow einfach nur die bestehenden Objekte verwaltet, und neue Funktionalitaet wie eben der Countdown einfach nur dadurch entsteht, dass man eine neue Klasse einfuehrt. Dadurch separiert man das konkrete Verhalten mit Countdown und dann Sternen-Scroller vom Grundgeruest, dass man Objekte hat, die eben getrieben werden.
Und genau dadurch wird GraphicsWindow eben NICHT zur Ablaufsteuerung benutzt. Natuerlich kannst du das machen, ist dein Code. Ich sage nur, wie ich das machen wuerde. Denn so kannst du zB spaeter ein Raumschiff-Objekt haben, und das spawnt Bullet-Objekte, und die Bullet-Objekte pruefen, ob sie mit Alien-Objekten kollidieren, und fuegt denen Schaden zu, und die Alien-Objekte entfernen sich selbst, wenn sie keine Lebensenergie mehr haben. Etc etc.
Mit Protokoll meine ich, wie das Fenster und die unterschiedlichen Objekte miteinander reden. Das die update-Methode einen Rueckgabewert hat, das ist eine Vereinbarung, wie sie miteinander kommunizieren, mit bestimmten Erwartungen etc. Also ein Protokoll.
Und ja, generisch heisst, dass das GraphicsWindow einfach nur die bestehenden Objekte verwaltet, und neue Funktionalitaet wie eben der Countdown einfach nur dadurch entsteht, dass man eine neue Klasse einfuehrt. Dadurch separiert man das konkrete Verhalten mit Countdown und dann Sternen-Scroller vom Grundgeruest, dass man Objekte hat, die eben getrieben werden.
Und genau dadurch wird GraphicsWindow eben NICHT zur Ablaufsteuerung benutzt. Natuerlich kannst du das machen, ist dein Code. Ich sage nur, wie ich das machen wuerde. Denn so kannst du zB spaeter ein Raumschiff-Objekt haben, und das spawnt Bullet-Objekte, und die Bullet-Objekte pruefen, ob sie mit Alien-Objekten kollidieren, und fuegt denen Schaden zu, und die Alien-Objekte entfernen sich selbst, wenn sie keine Lebensenergie mehr haben. Etc etc.
-
- User
- Beiträge: 407
- Registriert: Freitag 6. August 2010, 17:03
__deets__ hat geschrieben: ↑Dienstag 1. Januar 2019, 11:07 Denn du könntest ein Countdown-Objekt analog zu einem Star einführen. Und dann die _stars in _things oder so umbennennen. Am Anfang erzeugst du dann nur ein Countdown-Objekt. Dessen Update Methode wird dann immer mit der verflossenen Zeit aufgerufen & modifiziert sein Text-item (oder entfernt es und legt ein neues an). Mit scene clear darf dann natürlich nicht gearbeitet werden.
Wenn der Countdown abgelaufen ist, entfernt man das Objekt aus der Liste der Dinge, und fügt die Sterne ein. Dazu muss man ggf das Protokoll etwas erweitern: Update muss zurück geben, ob das Objekt weiter leben soll, oder entfernt werden muss. Und es muss eine neue Liste von Objekten zurück geben können. Man könnte also ein Tupel (True, []) zurück geben in update.
Auf die Art kannst du zb auch einen StarSpawner machen, der die Sterne nach und nach einführt.
Der entscheidende Aspekt aber ist, dass die “Engine” generisch bleibt.
Bei der Umsetzung der Empfehlungen komme ich nicht so recht weiter. Die `spawn_stars()`-Methode soll ins `countdown`-Objekt, um nach und nach Sterne einzuführen, obwohl das `countdown`-Objekt direkt nach dem Ablauf des Countdowns aus der Liste der Dinge auf dem Bildschirm entfernt wird. Hast du diese Empfehlung anders gemeint?__deets__ hat geschrieben: ↑Dienstag 1. Januar 2019, 23:09 Auf dem Weg ja, die spawn_stars Methode sollte natürlich uns Countdown Objekt. Und üblicherweise sammelt man die zu entfernenden Objekte auf, und macht das dann am Ende in einem Rutsch. Genauso fügt man dann neue erst am Ende wirklich hinzu & erst in nächsten Zyklus werden die gerechnet.
Hier stecke ich momentan fest:
Code: Alles auswählen
import sys
import time
from random import random
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtWidgets import QApplication, QGraphicsView, QGraphicsScene
from PyQt5.QtGui import QPainterPath
START_COUNTDOWN = 3
UPDATE_INTERVAL = 100
SPEED = 5000
MIN_DISTANCE = 30000
DISTANCE_VARIANCE = 20000
D = 1000
class Countdown:
def __init__(self, scene):
self.scene = scene
painterpath = QPainterPath()
self.scene.addPath(painterpath)
self._text = None
self.obsolet = False
self.things = None
def update(self, elapsed_time):
if self._text:
self.scene.removeItem(self._text)
remaining_time = (START_COUNTDOWN - elapsed_time)
self._text = self.scene.addText(f"In {remaining_time:.2} Sekunden gehts los!")
if remaining_time <= 0:
self.obsolet = True
return self.obsolet
def spawn_star(self):
self._things_on_screen.append(Star(self.scene))
class Star:
def __init__(self, scene):
painterpath = QPainterPath()
painterpath.addRect(-10, -10, 20, 20)
self._x = random() * 20000 - 10000
self._y = random() * 20000 - 10000
self._z = random() * 20000 + MIN_DISTANCE
self._speed = random() * SPEED
self._path = scene.addPath(painterpath)
self._draw_pen()
def _draw_pen(self):
self._path.setPen(Qt.red)
self._path.setBrush(Qt.transparent)
def _draw_brush(self):
self._path.setBrush(Qt.blue)
def _random_draw(self):
if random() > .5 == 0:
self._draw_pen()
else:
self._draw_brush()
def update(self, elapsed):
self._z -= elapsed * self._speed
if self._z < 0:
self._z = random() * DISTANCE_VARIANCE + MIN_DISTANCE
self._path.setX(self._x * self.D / self._z)
self._path.setY(self._y * self.D / self._z)
self._path.setScale(1 - self._z / (MIN_DISTANCE + DISTANCE_VARIANCE))
self._path.setRotation(self._path.rotation() + elapsed * 360)
if random() > .5 == 0:
self._random_draw()
obsolet = False
else:
obsolet = True
return obsolet
class GraphicsWindow(QGraphicsView):
def __init__(self, parent=None):
super().__init__(parent)
self._last = time.monotonic()
self.scene = QGraphicsScene(self)
self.setScene(self.scene)
self.scene.setSceneRect(-400, -400, 800, 800)
self.showFullScreen()
self.countdown = Countdown(self.scene)
self._things_on_screen = list()
self._things_on_screen.append(self.countdown)
self._timer = QTimer()
self._timer.setTimerType(Qt.PreciseTimer)
self._timer.setInterval(100)
self._timer.timeout.connect(self._update_all)
self._timer.start()
self._start = time.monotonic()
def _update_all(self):
now = time.monotonic()
elapsed = now - self._start
for thing in self._things_on_screen:
if thing.update(elapsed):
self._things_on_screen.clear() # Liste leeren, wenn Startcountdown abgelaufen.
def keyPressEvent(self, event):
if event.key() == Qt.Key_Escape:
self.close()
def main():
app = QApplication(sys.argv)
graphics_window = GraphicsWindow()
graphics_window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Atalanttore
Ich habe auch noch etwas anderes geschrieben, das du bisher ignoriert hast: die update-Methode soll nicht nur True/False zurueck geben, sondern ein TUPEL aus (ich-bin-obsolet, und-dies-sind-neue-objekte-die-du-bitte-im-naechsten-zyklus-mit-updatest).
Und aus diesen beiden Elementen ergibt sich dann, dass Countdown beim ablauf seiner Zeit (True, self._spawn_stars()) zurueck gibt.
Und aus diesen beiden Elementen ergibt sich dann, dass Countdown beim ablauf seiner Zeit (True, self._spawn_stars()) zurueck gibt.
-
- User
- Beiträge: 407
- Registriert: Freitag 6. August 2010, 17:03
@__deets__: Okay, aber die Methode `self._spawn_stars()` gehört doch zum Objekt `countdown`, dass nach dem Ablauf des Startcountdowns gelöscht wird, werden soll oder etwa nicht?
Gruß
Atalanttore
Gruß
Atalanttore
Ja. Und? Wenn das letzte, Countdown macht darin besteht, sich selbst fuer obsolet zu erklaeren, und gleichzeitig 100 Sterne zurueck gibt, die dann im naechsten Intervall alle berechnet werden, dann ist das doch auch kein Problem?
Jetzt sehe ich natuerlich auch einen Fehler, den du machst: das Countdown-Objekt muss sich doch die Sterne nicht merken. self._things hat in Countdown ueberhaupt nichts verloren.
Das Prinzip ist doch wirklich nicht schwer:
- das Window-Objekt verwaltet die Liste aller aktiven Objekte (self._things), und ruft update auf denen auf.
- update stellt das Objekt neu dar, und gibt zurueck, ob es weiterhin leben will, und eine Liste mit Objekten, die in Zukunft vom Window verwaltet werden sollen. Wenn die Liste leer ist, dann passiert da nix.
Jetzt sehe ich natuerlich auch einen Fehler, den du machst: das Countdown-Objekt muss sich doch die Sterne nicht merken. self._things hat in Countdown ueberhaupt nichts verloren.
Das Prinzip ist doch wirklich nicht schwer:
- das Window-Objekt verwaltet die Liste aller aktiven Objekte (self._things), und ruft update auf denen auf.
- update stellt das Objekt neu dar, und gibt zurueck, ob es weiterhin leben will, und eine Liste mit Objekten, die in Zukunft vom Window verwaltet werden sollen. Wenn die Liste leer ist, dann passiert da nix.
-
- User
- Beiträge: 407
- Registriert: Freitag 6. August 2010, 17:03
Also gleichzeitig ...
... aber ich habe mit nach und nach einführen zeitlich versetzt nacheinander verstanden.
Gruß
Atalanttore
Und hat sich das auf Countdown bezogen, oder auf ein hypothetischen "StarSpawner"-Objekt, das ich beschrieben habe? Da musst du schon genau lesen.
Das Grundprinzip des Mechanismus ist doch viel flexibler, als dein use-case: du kannst schon gleich im ersten update eines Objektes mehr Sterne (oder was auch immer) entstehen lassen. Oder ueber die Zeit. Oder beim letzten update. Logischerweise NICHT danach...
Das Grundprinzip des Mechanismus ist doch viel flexibler, als dein use-case: du kannst schon gleich im ersten update eines Objektes mehr Sterne (oder was auch immer) entstehen lassen. Oder ueber die Zeit. Oder beim letzten update. Logischerweise NICHT danach...
So, hier mal mit dem StarSpawner, weil ich keine Lust hatte, den Countdown zu implementieren:
Code: Alles auswählen
import random
import time
import sys
from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication, QGraphicsView, QGraphicsScene
from PyQt5.QtGui import QPainterPath
points = []
SPEED = 5000
MIN_DISTANCE = 30000
DISTANCE_VARIANCE = 20000
class Star:
D = 1000 # Durchmesser?
def __init__(self, scene):
path = QPainterPath()
path.addRect(-10, -10, 20, 20)
self._x, self._y = random.random() * 20000 - 10000, random.random() * 20000 - 10000
self._z = random.random() * 20000 + MIN_DISTANCE
self._speed = random.random() * SPEED
self._path = scene.addPath(path)
self._path.setPen(QtCore.Qt.red)
self.update(0) # force placement
def update(self, elapsed):
self._z -= elapsed * self._speed # z-Koordinate neu berechnen
if self._z < 0:
self._z = random.random() * DISTANCE_VARIANCE + MIN_DISTANCE # Zufallszahl * Multiplikator + Mindestabstand
self._path.setX(self._x * self.D / self._z) # ?
self._path.setY(self._y * self.D / self._z) # ?
self._path.setScale(1 - self._z / (MIN_DISTANCE + DISTANCE_VARIANCE)) # Sterne bei der Bewegung nach vorne größer skalieren
self._path.setRotation(self._path.rotation() + elapsed * 360) # Drehbewegung der Sterne
return False, []
class StarSpawner:
STARS_PER_SECOND = 10
TOTAL_STARS = 100
def __init__(self, scene):
self._star_spawn_period = 1.0 / self.STARS_PER_SECOND
self._accu = 0.0
self._count = self.TOTAL_STARS
self._scene = scene
def update(self, elapsed):
self._accu += elapsed
spawned_stars = []
while self._accu > self._star_spawn_period:
spawned_stars.append(Star(self._scene))
self._accu -= self._star_spawn_period
self._count -= len(spawned_stars)
return self._count <= 0, spawned_stars
class GraphicsWindow(QGraphicsView):
def __init__(self, parent=None):
super(GraphicsWindow, self).__init__(parent)
self._last = time.monotonic()
scene = QGraphicsScene(self)
self.setScene(scene) #
self._things = [StarSpawner(scene)]
scene.setSceneRect(-400, -400, 800, 800)
self.setCacheMode(QGraphicsView.CacheBackground)
self._timer = QtCore.QTimer(self)
self._timer.setSingleShot(False)
self._timer.setInterval(1000 / 60)
self._timer.timeout.connect(self._update_all)
self._timer.start()
def _update_all(self):
now = time.monotonic()
elapsed = now - self._last
self._last = now
to_remove = []
total_to_add = []
for thing in self._things:
remove, to_add = thing.update(elapsed)
if remove:
to_remove.append(thing)
total_to_add.extend(to_add)
self._things = total_to_add + \
[thing for thing in self._things if thing not in to_remove]
def main():
app = QApplication(sys.argv)
graphics_window = GraphicsWindow()
graphics_window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
@__deets__: statt ein total_to_add und ein to_remove wäre doch eine einzelne Liste, die bei not remove things und immer to_add hinzufügt, einfacher umzusetzen. Dann könnte man statt ein remove und ein to_add nur eine Liste zurückgeben, die normalerweise [self] enthält, oder nichts, oder irgendwelche Objekte, die neu erstellt wurden.
Auch ne gute Idee. Das Protokoll ist insgesamt eh ein bisschen aus der Hüfte. Wenn man das weiter treibt, will man ja irgendwann ein Engine Objekt oder so, auf dem dann ggf direkt Methoden ala addItem und removeMe oder so aufgerufen werden, und zb auch andere Objekte gefunden werden können (zb homing missile die in Richtung Spieler fliegt)
. Aber für den bestehenden Entwurf ist dein Ansatz minimaler.
. Aber für den bestehenden Entwurf ist dein Ansatz minimaler.
-
- User
- Beiträge: 407
- Registriert: Freitag 6. August 2010, 17:03
@__deets__: Danke für den Code.
Die Variablen `remove` und `to_remove` und noch weitere Anweisungen konnte ich verkürzen bzw. ganz auskommentieren und der Code läuft immer noch.
Gruß
Atalanttore
Die Variablen `remove` und `to_remove` und noch weitere Anweisungen konnte ich verkürzen bzw. ganz auskommentieren und der Code läuft immer noch.
Code: Alles auswählen
import random
import time
import sys
from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication, QGraphicsView, QGraphicsScene
from PyQt5.QtGui import QPainterPath
SPEED = 5000
MIN_DISTANCE = 30000
DISTANCE_VARIANCE = 20000
class Star:
D = 1000
def __init__(self, scene):
path = QPainterPath()
path.addRect(-10, -10, 20, 20)
self._x, self._y = random.random() * 20000 - 10000, random.random() * 20000 - 10000
self._z = random.random() * 20000 + MIN_DISTANCE
self._speed = random.random() * SPEED
self._path = scene.addPath(path)
self._path.setPen(QtCore.Qt.red)
self.update(0) # force placement
def update(self, elapsed):
self._z -= elapsed * self._speed
if self._z < 0:
self._z = random.random() * DISTANCE_VARIANCE + MIN_DISTANCE
self._path.setX(self._x * self.D / self._z)
self._path.setY(self._y * self.D / self._z)
self._path.setScale(1 - self._z / (MIN_DISTANCE + DISTANCE_VARIANCE))
self._path.setRotation(self._path.rotation() + elapsed * 360)
# return False, []
return []
class StarSpawner:
STARS_PER_SECOND = 10
TOTAL_STARS = 100
def __init__(self, scene):
self._star_spawn_period = 1.0 / self.STARS_PER_SECOND
self._accu = 0.0
#self._count = self.TOTAL_STARS
self._scene = scene
def update(self, elapsed):
self._accu += elapsed
spawned_stars = []
while self._accu > self._star_spawn_period:
spawned_stars.append(Star(self._scene))
self._accu -= self._star_spawn_period
#self._count -= len(spawned_stars)
#return self._count <= 0, spawned_stars
return spawned_stars
class GraphicsWindow(QGraphicsView):
def __init__(self, parent=None):
super(GraphicsWindow, self).__init__(parent)
self._last = time.monotonic()
scene = QGraphicsScene(self)
self.setScene(scene)
self._things = [StarSpawner(scene)]
scene.setSceneRect(-400, -400, 800, 800)
self.setCacheMode(QGraphicsView.CacheBackground)
self._timer = QtCore.QTimer(self)
self._timer.setSingleShot(False)
self._timer.setInterval(1000 / 60)
self._timer.timeout.connect(self._update_all)
self._timer.start()
def _update_all(self):
now = time.monotonic()
elapsed = now - self._last
self._last = now
# to_remove = []
total_to_add = []
for thing in self._things:
to_add = thing.update(elapsed)
# remove, to_add = thing.update(elapsed)
# if remove:
# to_remove.append(thing)
total_to_add.extend(to_add)
self._things = total_to_add + \
[thing for thing in self._things]
# [thing for thing in self._things if thing not in to_remove]
def main():
app = QApplication(sys.argv)
graphics_window = GraphicsWindow()
graphics_window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Atalanttore
Jetzt kannst Du keine Elemente mehr entfernen, da `[thing for thing in self._things]` ohne Bedingung alle alten Elemente weiterverwendet. Wie schon geschrieben, der Teil muß weg, und update wenn nötig sich selbst zurückgeben.
-
- User
- Beiträge: 407
- Registriert: Freitag 6. August 2010, 17:03
@Sirius3: Du meinst anscheinend folgenden Tipp an __deets__.
@__deets__ & Sirius3: Ich habe den Code nun so kommentiert wie ich ihn mit Debugger und eingefügten Statusmeldungen verstanden habe. Ist das so richtig?
Gruß
Atalanttore
Was meinst du mit "normalerweise [self] enthält, oder nichts, oder irgendwelche Objekte" genau?Sirius3 hat geschrieben: ↑Freitag 4. Januar 2019, 22:03 @__deets__: statt ein total_to_add und ein to_remove wäre doch eine einzelne Liste, die bei not remove things und immer to_add hinzufügt, einfacher umzusetzen. Dann könnte man statt ein remove und ein to_add nur eine Liste zurückgeben, die normalerweise [self] enthält, oder nichts, oder irgendwelche Objekte, die neu erstellt wurden.
@__deets__ & Sirius3: Ich habe den Code nun so kommentiert wie ich ihn mit Debugger und eingefügten Statusmeldungen verstanden habe. Ist das so richtig?
Code: Alles auswählen
import random
import time
import sys
from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication, QGraphicsView, QGraphicsScene
from PyQt5.QtGui import QPainterPath
SPEED = 5000
MIN_DISTANCE = 30000
DISTANCE_VARIANCE = 20000
class Star:
D = 1000
def __init__(self, scene):
path = QPainterPath()
path.addRect(-10, -10, 20, 20)
self._x, self._y = random.random() * 20000 - 10000, random.random() * 20000 - 10000
self._z = random.random() * 20000 + MIN_DISTANCE
self._speed = random.random() * SPEED
self._path = scene.addPath(path)
self._path.setPen(QtCore.Qt.red)
self.update(0) # force placement
def update(self, elapsed):
"""
Rückgabe:
'False', damit Star-Objekt nicht entfernt wird.
Leere Liste, weil hier nix generiert wird.
"""
self._z -= elapsed * self._speed
if self._z < 0:
self._z = random.random() * DISTANCE_VARIANCE + MIN_DISTANCE
self._path.setX(self._x * self.D / self._z) # ?
self._path.setY(self._y * self.D / self._z) # ?
self._path.setScale(1 - self._z / (MIN_DISTANCE + DISTANCE_VARIANCE))
self._path.setRotation(self._path.rotation() + elapsed * 360)
return False, []
class StarSpawner:
STARS_PER_SECOND = 10
TOTAL_STARS = 100
def __init__(self, scene):
self._star_spawn_period = 1.0 / self.STARS_PER_SECOND
self._accu = 0.0
self._count = self.TOTAL_STARS
self._scene = scene
def update(self, elapsed):
"""
Bis self._accu kleiner/gleich der self._star_spawn_period ist
werden neue Star-Objekte generiert.
Sobald 100 Sterne generiert wurden, wird 'False' zurückgegeben, damit der
StarSpawner aus self._things entfernt wird.
"""
self._accu += elapsed
spawned_stars = []
while self._accu > self._star_spawn_period:
spawned_stars.append(Star(self._scene))
self._accu -= self._star_spawn_period
self._count -= len(spawned_stars)
return self._count <= 0, spawned_stars #
class GraphicsWindow(QGraphicsView):
def __init__(self, parent=None):
super(GraphicsWindow, self).__init__(parent)
self._last = time.monotonic()
scene = QGraphicsScene(self)
self.setScene(scene)
self._things = [StarSpawner(scene)]
scene.setSceneRect(-400, -400, 800, 800)
self._timer = QtCore.QTimer(self)
self._timer.setSingleShot(False)
self._timer.setInterval(1000 / 60)
self._timer.timeout.connect(self._update_all)
self._timer.start()
def _update_all(self):
"""
remove: True => Objekt entfernen (trifft nur bei StarSpawner zu); False => Objekt behalten
to_add: StarSpawner => Liste mit erzeugten Star-Objekten; Star => leere Liste
to_remove: nur der StarSpawner kommt darin einmal vor
'self._things' werden die Elemente aus 'total_to_add' und alle eigenen Elemente,
die nicht in 'to_remove' vorkommen (list comprehension), neu zugewiesen.
In 'to_remove' vorkommende Elemente (nur StarSpawner) werden so entfernt.
"""
now = time.monotonic()
elapsed = now - self._last
self._last = now
to_remove = []
total_to_add = []
for thing in self._things:
length = len(self._things) # Statusmeldung
print(f"self._things (Länge: {length}): {self._things}") # Statusmeldung
remove, to_add = thing.update(elapsed)
if to_add: # Statusmeldung
print(f"to_add (Zeit: {time.monotonic()}): {to_add}")
if remove:
to_remove.append(thing)
print(f"to_remove (Zeit: {time.monotonic()}): {to_remove}") # Statusmeldung
total_to_add.extend(to_add)
if total_to_add: # Statusmeldung
print(f"total_to_add: (Zeit: {time.monotonic()}): {total_to_add}")
self._things = total_to_add + \
[thing for thing in self._things if thing not in to_remove]
def main():
app = QApplication(sys.argv)
graphics_window = GraphicsWindow()
graphics_window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Atalanttore
Die Kommentare kommen halbwegs hin.
Was Sirius3 meinte: Statt (True, []) etc zurück zu geben, einfach immer eine Liste zurück geben. Diese Liste ist beim Star zb immer [self]. Wie sie beim StarSpawner aussieht, kannst du ja mal überlegen.
In update_all wird dann immer die neue Liste aus den Rückgaben der alten berechnet. Ohne abfragen etc.
Was Sirius3 meinte: Statt (True, []) etc zurück zu geben, einfach immer eine Liste zurück geben. Diese Liste ist beim Star zb immer [self]. Wie sie beim StarSpawner aussieht, kannst du ja mal überlegen.
In update_all wird dann immer die neue Liste aus den Rückgaben der alten berechnet. Ohne abfragen etc.
-
- User
- Beiträge: 407
- Registriert: Freitag 6. August 2010, 17:03
@__deets__: Halbwegs habe ich es jetzt auch verstanden.
Der StarSpawner gibt schon jetzt eine Liste (`spawned_stars`) mit den neu von ihm erstellten Star-Objekten zurück. Neben dem booleschen Wert, ob der StarSpawner noch gebraucht wird, bräuchte man das eigentlich auch weiterhin oder man verzichtet auf den booleschen Wert und fügt den StarSpawner als `self` auch zur Liste der von ihm erstellten Star-Objekte hinzu. Wenn der StarSpawner merkt, dass er seine Arbeit erledigt hat (100 Sterne erzeugt), könnte er sich nicht mehr selbst zur Liste hinzufügen. Man müsste aber irgendwie sicherstellen, dass `self._things` am Ende nicht mehrere StarSpawner-Objekte enthält, sondern keine.
Weitere Fragen:
Atalanttore
Der StarSpawner gibt schon jetzt eine Liste (`spawned_stars`) mit den neu von ihm erstellten Star-Objekten zurück. Neben dem booleschen Wert, ob der StarSpawner noch gebraucht wird, bräuchte man das eigentlich auch weiterhin oder man verzichtet auf den booleschen Wert und fügt den StarSpawner als `self` auch zur Liste der von ihm erstellten Star-Objekte hinzu. Wenn der StarSpawner merkt, dass er seine Arbeit erledigt hat (100 Sterne erzeugt), könnte er sich nicht mehr selbst zur Liste hinzufügen. Man müsste aber irgendwie sicherstellen, dass `self._things` am Ende nicht mehrere StarSpawner-Objekte enthält, sondern keine.
Weitere Fragen:
- Wieso heißt die Variable, wo die vergangene Zeit (`elapsed`) aufaddiert wird `self._accu`?
- Braucht man für weitere Objekte, die man zur Liste `self._things` hinzufügen möchte, damit sie auf dem Bildschirm erscheinen, auch jeweils einen Spawner?
Atalanttore
Man muss nicht sicherstellen, dass mehrere entstehen, weil die Liste _things immer NEU gebaut wird aus allen Rueckgaben der update-Methoden-Aufrufe. Deshalb muss ein Star eben [self] zurueckgeben.
Zu deinen Fragen:
1) weil mir kein besserer Name eingefallen ist. Akkumulator (accu) ist ein Register-Name in alten Prozessoren, und manchmal benutze ich den Namen fuer Dinge, die etwas "aufsammeln", also akkumulieren. Da kommt der Name urspruenglich denke ich her.
2) Das ist eine Moeglichkeit. Das kommt halt drauf an, was da jetzt so an Drehbuch geplant ist. Man kann sich da beliebig viel denken, aber dann muss man ggf. auch noch andere Massnahmen ergreifen. Dadurch aendert sich dann halt das Vorgehen. Dieses Muster hier ist fuer den gegebenen Zweck gut, weil es eine Ablaufsteuerung mit klar definierten Verantwortlichkeiten herstellt, aber mehr Logik mag dann andere Dinge notwendig machen.
Code: Alles auswählen
things_for_this_frame = self._things
self._things = [] # neu!
for thing in things_for_this_frame:
self._things.extend(thing.update())
1) weil mir kein besserer Name eingefallen ist. Akkumulator (accu) ist ein Register-Name in alten Prozessoren, und manchmal benutze ich den Namen fuer Dinge, die etwas "aufsammeln", also akkumulieren. Da kommt der Name urspruenglich denke ich her.
2) Das ist eine Moeglichkeit. Das kommt halt drauf an, was da jetzt so an Drehbuch geplant ist. Man kann sich da beliebig viel denken, aber dann muss man ggf. auch noch andere Massnahmen ergreifen. Dadurch aendert sich dann halt das Vorgehen. Dieses Muster hier ist fuer den gegebenen Zweck gut, weil es eine Ablaufsteuerung mit klar definierten Verantwortlichkeiten herstellt, aber mehr Logik mag dann andere Dinge notwendig machen.