Geometrische Objekte werden generiert, aber nicht angezeigt.
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.
-
- User
- Beiträge: 407
- Registriert: Freitag 6. August 2010, 17:03
@__deets__:
Zu 2. Kann man auch einen generischen Spawner bauen, der verschiedene Objekte erstellt?
Danke für den Codeabschnitt.
Ich habe den Code nun eingebaut, `elapsed` als Parameter für die `update()`-Methoden ergänzt (weil sich Python über ein fehlendes Argument beschwerte) und die Rückgabewerte so angepasst wie ich es verstanden habe. Die Liste `self._things` bleibt jetzt allerdings leer und es kommt zu folgendem Fehler:
Stimmt mit den angepassten Rückgaben etwas nicht?
Code:
Gruß
Atalanttore
Zu 2. Kann man auch einen generischen Spawner bauen, der verschiedene Objekte erstellt?
Danke für den Codeabschnitt.
Ich habe den Code nun eingebaut, `elapsed` als Parameter für die `update()`-Methoden ergänzt (weil sich Python über ein fehlendes Argument beschwerte) und die Rückgabewerte so angepasst wie ich es verstanden habe. Die Liste `self._things` bleibt jetzt allerdings leer und es kommt zu folgendem Fehler:
Code: Alles auswählen
Traceback (most recent call last):
File "/home/ata/source/stars-qt/main.py", line 91, in _update_all
self._things.extend(thing.update(elapsed))
TypeError: 'NoneType' object is not iterable
Code:
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 self
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.append(self)
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):
now = time.monotonic()
elapsed = now - self._last
self._last = now
things_for_this_frame = self._things
self._things = [] # neu!
for thing in things_for_this_frame:
self._things.extend(thing.update(elapsed))
def main():
app = QApplication(sys.argv)
graphics_window = GraphicsWindow()
graphics_window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Atalanttore
Schau mal genau hin. Was habe ich geschrieben, was gibt dein Stern zurück? und was gibt “append” in deinem spawner zurück.
Und natürlich kann man einen anderen spawner bauen. Jedes Objekt, das diese Schnittstelle implementiert, geht.
Und natürlich kann man einen anderen spawner bauen. Jedes Objekt, das diese Schnittstelle implementiert, geht.
-
- User
- Beiträge: 407
- Registriert: Freitag 6. August 2010, 17:03
`Star` gibt sich selbst, also ein `Star`-Objekt zurück. Im `StarSpawner` wird an die Liste `spawned_stars` mit den von ihm erzeugten `Star`-Objekten noch das `StarSpawner`-Objekt angehängt und dann zurückgegeben.
Gruß
Atalanttore
Gruß
Atalanttore
Und wenn ein Star sich selbst zurueck gibt - wie verhaelt sich das zu der Erwartung an den Rueckgabewert an der aufrufenden Stelle?
Und zu der zweiten Antwort: das ist das, was du *denkst* was passiert. Nochmal die Frage: was ist der Rueckgabewert von liste.append()? Bau dir ein Skript, pack ne Liste rein, und mach print(liste.append(10)). Dann sollte der Fehler hoffentlich klar werden.
Und zu der zweiten Antwort: das ist das, was du *denkst* was passiert. Nochmal die Frage: was ist der Rueckgabewert von liste.append()? Bau dir ein Skript, pack ne Liste rein, und mach print(liste.append(10)). Dann sollte der Fehler hoffentlich klar werden.
-
- User
- Beiträge: 407
- Registriert: Freitag 6. August 2010, 17:03
@__deets__: `return self` im `Star`-Objekt liefert sich selbst zurück und `spawned_stars.append(Star(self._scene))` im `StarSpawner`-Objekt erwartet ein `Star`-Objekt.
`append()` scheint nicht innerhalb einer `print`-Anweisung zu funktionieren. Warum auch immer ...
Skript:
Ausgabe:
Gruß
Atalanttore
`append()` scheint nicht innerhalb einer `print`-Anweisung zu funktionieren. Warum auch immer ...
Skript:
Code: Alles auswählen
liste = list(range(0,10))
liste2 = liste.copy()
liste2.append(10)
print("Liste 1:", liste.append(10))
print("Liste 2:", liste2)
Code: Alles auswählen
Liste 1: None
Liste 2: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Atalanttore
Seufz. Gut, mit den Hinweisen zum selbst-verstehen hoere ich jetzt mal auf.
Die update-Methode eines things muss IMMER eine Liste zurueckgeben. Wenn du also ` return self` in Star.update machst, dann ist das *trommelwirbel* keine Liste. Du musst [self] zurueck geben.
Und wenn du `return spawned_stars.append(self)` machst im Spawner, dann gibt das nicht magisch die Liste der erzeugten Sterne erweitert um das self des Spawners zurueck, sondern was auch immer list.append zurueckgibt. Und list.append gib *trommelwirbel* None zurueck.
Du musst also
daraus machen, oder wegen mir
Wobei durch das unbedingte zurueckgeben natuerlich der Spawner niemals aufhoert, Sterne zu erzeugen. Und irgendwann deine Framerate einbricht. Aber das ist dann das naechste Problem.
Die update-Methode eines things muss IMMER eine Liste zurueckgeben. Wenn du also ` return self` in Star.update machst, dann ist das *trommelwirbel* keine Liste. Du musst [self] zurueck geben.
Und wenn du `return spawned_stars.append(self)` machst im Spawner, dann gibt das nicht magisch die Liste der erzeugten Sterne erweitert um das self des Spawners zurueck, sondern was auch immer list.append zurueckgibt. Und list.append gib *trommelwirbel* None zurueck.
Du musst also
Code: Alles auswählen
spawned_stars.append(self)
return spawned_stars
Code: Alles auswählen
return spawned_stars + [self]
Nachtrag: natuerlich funktioniert append innerhalb einer print-Anweisung. Es gibt aber eben None zurueck, und das wird dann eben ausgedruckt. Du hast halt *geglaubt*, dass es die Liste erweitert um das angehangene Element zurueckgibt. Genau auf diesen Denkfehler wollte ich dich hinweisen.
-
- User
- Beiträge: 407
- Registriert: Freitag 6. August 2010, 17:03
@__deets__: Mit deinen Änderungen funktioniert es dann auch.
Ich habe die `update()`-Methode im `StarSpawner` angepasst, damit nicht mehr als 100 Sterne erzeugt werden.
Gruß
Atalanttore
Ich habe die `update()`-Methode im `StarSpawner` angepasst, damit nicht mehr als 100 Sterne erzeugt werden.
Code: Alles auswählen
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)
if self._count > 0:
spawned_stars.append(self)
#return self._count <= 0, spawned_stars
return spawned_stars
Atalanttore
-
- User
- Beiträge: 407
- Registriert: Freitag 6. August 2010, 17:03
Für eine Erweiterung des Programms bin ich auf der Suche nach einer einfachen Möglichkeiten zum Erkennen, wenn ein Objekt in der `scene` für den Nutzer nicht mehr sichtbar ist. Laut Doku bietet `QGraphicsScene` eine solche Möglichkeit an:
Gruß
Atalanttore
Eine passende Methode und/oder eine genau Erklärung gibt es dort aber leider nicht.QGraphicsScene also provides functionality that lets you efficiently determine both the location of items, and for determining what items are visible within an arbitrary area on the scene.
Gruß
Atalanttore
Ich sehe da diverse. Zb https://doc.qt.io/qt-5/qgraphicsscene.h ... ectionArea - die ausgewählten items sind diejenigen die in dem Pfad liegen.
Und dann alle items Methoden https://doc.qt.io/qt-5/qgraphicsscene.html#items
-
- User
- Beiträge: 407
- Registriert: Freitag 6. August 2010, 17:03
Wenn `path` ein `QPainterPath`-Objekt und `scene` ein `QGraphicsScene`-Objekt ist, schreibt man das dann so:
Es wäre toll, wenn `selected` dann eine Liste mit den noch sichtbaren Objekten enthält. Ist das der Fall?
Gruß
Atalanttore
Code: Alles auswählen
scene.setSelectionArea(path)
selected = scene.selectedItems()
Gruß
Atalanttore
Ich wuerde eher mit der zweiten Option operieren, die Selektion ist sicher moeglich, aber mir persoenlich zu viel Seiteneffekt - die Selektion hat ja ggf. auch noch andere Einsatzzwecke.
Und hast du das mal probiert? Du hast doch inzwischen ausreichend viel Code, um damit mal rumzuspielen.
Und hast du das mal probiert? Du hast doch inzwischen ausreichend viel Code, um damit mal rumzuspielen.
-
- User
- Beiträge: 407
- Registriert: Freitag 6. August 2010, 17:03
Also eher so:
Gruß
Atalanttore
Code: Alles auswählen
scene.setSelectionArea(path)
selected = scene.items()
Atalanttore