1) Keine Ahnung, 1996 oder so. Wieso ist das wichtig?
2) das ist irgendwie keine Frage, sondern eine Feststellung...
3) Keinen. Das ist hauptsaechlich DEIN Beispiel, lediglich zum laufen gebracht. Du siehst doch, dass da keine .ui-Datei vorkommt. Oder anderweitig generiert aussieht. Der Hinweis war gemeint als "nimm dir den Designer, bau einen GraphicsView ein, und manipulier den oder das parent-widget/layout (keine Ahnung was genau), lad die UI Datei, und gucks' dir an". Der Designer prasentiert einem eben all die Layout und sonstigen Optionen auf leicht erschliessbare Art & Weise, und darum benutze ich den fuer sowas - wenn ich das selbst machen wuerde, und eben nicht nur dein Beispiel aufgebohrt haette.
Geometrische Objekte werden generiert, aber nicht angezeigt.
-
- User
- Beiträge: 407
- Registriert: Freitag 6. August 2010, 17:03
@__deets__:
Zu 1.: ~22 Jahre könnte ich mir solche Details nicht im Kopf merken.
Zu 3.: Alles klar.
Gruß
Atalanttore
Zu 1.: ~22 Jahre könnte ich mir solche Details nicht im Kopf merken.
Zu 3.: Alles klar.
Gruß
Atalanttore
Naja, das ist ja eine simple Division. Du kannst dir doch sicher Dinge merken wie zb die Fläche eines Kreises zu berechnen, etc. das ist ja nichts anderes. Und es war ein ziemliches aha-Erlebnis, weil ich vorher nur einen simpleren Algorithmus kannte, der einfach jeden Stern mit fixen Schritten bewegt. Das war eher C64-Style. Darum war das so ein “wichtiges” Ding.
-
- User
- Beiträge: 407
- Registriert: Freitag 6. August 2010, 17:03
Heute wollte ich die Sterne aus dem Beispiel blinken lassen. Dazu wollte ich zufällig entweder eine rote Außenlinie mit der Methode `setPen()` oder eine blaue Füllung mit der Methode `setBrush()` zeichnen lassen. Nun erscheinen selbst bei langsamer Geschwindigkeit blau gefüllte Rechtecke mit roter Außenlinie und nichts blinkt.
Anscheinend muss man die von `setPen()` oder `setBrush()` gezeichnete Außenlinie bzw. Füllung auch wieder löschen, wenn sie nicht mehr erscheinen soll. Leider habe ich in der Doku nichts passendes dazu gefunden.
Macht man das überhaupt so wie von mir gedacht?
Der Code:
Gruß
Atalanttore
Anscheinend muss man die von `setPen()` oder `setBrush()` gezeichnete Außenlinie bzw. Füllung auch wieder löschen, wenn sie nicht mehr erscheinen soll. Leider habe ich in der Doku nichts passendes dazu gefunden.
Macht man das überhaupt so wie von mir gedacht?
Der Code:
Code: Alles auswählen
import sys
import random
import time
from random import randint
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
def __init__(self, scene):
path = QPainterPath()
path.addRect(-10, -10, 20, 20)
self._x = random.random() * 20000 - 10000
self._y = random.random() * 20000 - 10000
self._z = random.random() * 20000 + MIN_DISTANCE
self._speed = random.random() * SPEED
self._path = scene.addPath(path)
self.draw_pen()
def draw_pen(self):
self._path.setPen(QtCore.Qt.red)
def draw_brush(self):
self._path.setBrush(QtCore.Qt.blue)
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)
if randint(0, 2) == 0:
self.draw_pen()
print("Pen")
else:
self.draw_brush()
print("Brush")
class GraphicsWindow(QGraphicsView):
def __init__(self, parent=None):
super().__init__(parent)
self._last = time.monotonic()
scene = QGraphicsScene(self)
self.setScene(scene)
scene.setSceneRect(-400, -400, 800, 800)
self.showFullScreen()
self._stars = []
for i in range(100):
self._stars.append(Star(scene))
self._timer = QtCore.QTimer(self)
self._timer.setInterval(1000 / 30)
self._timer.timeout.connect(self._update_all)
self._timer.start()
def _update_all(self):
now = time.monotonic()
elapsed = now - self._last
self._last = now
for star in self._stars:
star.update(elapsed)
def keyPressEvent(self, event):
if event.key() == QtCore.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
- __blackjack__
- User
- Beiträge: 13107
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
@Atalanttore: Man muss halt in beiden Fällen Pen und Brush setzen. Jeweils so dass man das eine sieht und das andere nicht. Zum Beispiel könntest Du die Farbe `Qt.transparent` für den jeweiligen Teil den man nicht sehen soll verwenden. Das würde ich allerdings nicht als blinken, sondern als flackern bezeichnen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
-
- User
- Beiträge: 407
- Registriert: Freitag 6. August 2010, 17:03
@__blackjack__: Also war ich gedanklich schon auf dem richtigen Weg.
`Qt.transparent` habe ich bisher noch nie verwendet, aber das bringt mich gleich zur nächsten Frage.
In meinem Code habe ich das Modul `QtCore` komplett eingebunden.
Um auf den Wert von `Qt.transparent` zugreifen können, muss ich immer `QtCore` voranstellen.
Nach meinem Verständnis müsste `Qt.transparent` aber ohne vorangestelltem `QtCore` erreichbar sein, aber das führt zu einem NameError.
`Qt.transparent` ist als Klassenvariable `transparent = 19` in der Klasse `Qt(__sip.simplewrapper)` definiert. Komischerweise in Kleinbuchstaben `transparent` und nicht in Großbuchstaben wie eine Konstante, was es nach meinem Verständnis aber ist.
Gibt es einen Unterschied bei dem Import von Methoden und Klassenvariablen?
Gruß
Atalanttore
`Qt.transparent` habe ich bisher noch nie verwendet, aber das bringt mich gleich zur nächsten Frage.
In meinem Code habe ich das Modul `QtCore` komplett eingebunden.
Code: Alles auswählen
from PyQt5 import QtCore
Code: Alles auswählen
QtCore.Qt.transparent
Code: Alles auswählen
NameError: name 'Qt' is not defined
Gibt es einen Unterschied bei dem Import von Methoden und Klassenvariablen?
Gruß
Atalanttore
- __blackjack__
- User
- Beiträge: 13107
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
@Atalanttore: Wie kommst Du darauf das `Qt.transparent` funktionieren sollte wenn Du dieses `Qt` vorher nicht explizit importiert hast? Oder anders, wenn ``from PyQt5 import QtCore`` den Namen `Qt` auf Modulebene zur Verfügung stellen würde, dann vielleicht auch `Rainbow`, oder `Unicorn`, oder … – wie würde man den da den Überblick behalten wollen?
Die Namensschreibweisen orientieren sich halt an den Namen die die Qt-Entwickler in C++ verwendet haben. Und die haben sich halt entschieden Werte von einigen Aufzählungstypen in Kleinbuchstaben zu schreiben. Bei anderen verwenden sie MixedCase. Methodennamen entsprechen ja auch nicht den Python-Konventionen.
Da man weder Methoden noch Klassenvariablen importieren kann, gibt es da keinen Unterschied zwischen den beiden bezüglich des Imports.
Die Namensschreibweisen orientieren sich halt an den Namen die die Qt-Entwickler in C++ verwendet haben. Und die haben sich halt entschieden Werte von einigen Aufzählungstypen in Kleinbuchstaben zu schreiben. Bei anderen verwenden sie MixedCase. Methodennamen entsprechen ja auch nicht den Python-Konventionen.
Da man weder Methoden noch Klassenvariablen importieren kann, gibt es da keinen Unterschied zwischen den beiden bezüglich des Imports.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
-
- User
- Beiträge: 407
- Registriert: Freitag 6. August 2010, 17:03
@__blackjack__: Also ist `Qt` die unterste Klasse die man importieren kann. So funktioniert der Code dann auch:
Wieder was gelernt.
BTW: Bei mir enthält die Klasse `Qt` ganze 1149 Klassenvariablen. `Key_Escape` bekommt darin den Wert `16777216` zugewiesen.
Gruß
Atalanttore
Code: Alles auswählen
from PyQt5.QtCore import Qt, QTimer
BTW: Bei mir enthält die Klasse `Qt` ganze 1149 Klassenvariablen. `Key_Escape` bekommt darin den Wert `16777216` zugewiesen.
Gruß
Atalanttore
-
- User
- Beiträge: 407
- Registriert: Freitag 6. August 2010, 17:03
Zu Übungszwecken habe ich in der Klasse `GraphicsWindow` einen Startcountdown ergänzt. Was haltet ihr von meinem Startcountdown-Code?
Code:
PS: Ein frohes neues Jahr!
Gruß
Atalanttore
Code:
Code: Alles auswählen
import sys
import random
import time
from random import randint
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtWidgets import QApplication, QGraphicsView, QGraphicsScene
from PyQt5.QtGui import QPainterPath
points = []
SPEED = 5000
MIN_DISTANCE = 30000
DISTANCE_VARIANCE = 20000
COUNTDOWN = 5000
COUNTDOWN_UPDATE_INTERVAL = 100
class Star:
D = 1000
def __init__(self, scene):
path = QPainterPath()
path.addRect(-10, -10, 20, 20)
self._x = random.random() * 20000 - 10000
self._y = random.random() * 20000 - 10000
self._z = random.random() * 20000 + MIN_DISTANCE
self._speed = random.random() * SPEED
self._path = scene.addPath(path)
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.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 randint(0, 2) == 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._elapsed_timer = QTimer(self)
self._elapsed_timer.timeout.connect(self.update_countdown)
self._elapsed_time = 0
self.update_countdown()
def update_countdown(self):
self.scene.clear()
self.scene.addText(f"In {str((COUNTDOWN - self._elapsed_time) / 1000)} Sekunden gehts los!")
self._elapsed_timer.start(COUNTDOWN_UPDATE_INTERVAL)
self._elapsed_time += COUNTDOWN_UPDATE_INTERVAL
if self._elapsed_time == COUNTDOWN:
self._elapsed_timer.stop()
self.generate_stars()
def generate_stars(self):
self.scene.clear()
self._stars = []
for i in range(100):
self._stars.append(Star(self.scene))
self._star_timer = QTimer(self)
self._star_timer.setInterval(1000 / 30)
self._star_timer.timeout.connect(self._update_all)
self._star_timer.start()
def _update_all(self):
now = time.monotonic()
elapsed = now - self._last
self._last = now
for star in self._stars:
star.update(elapsed)
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()
Gruß
Atalanttore
@Atalanttore: was soll die globale Liste `points`? Warum werden manche Konstanten als global, andere als Klassenattribute definiert? Warum wird einmal nur das Modul `random` importiert und dann noch `randint` extra?
Ich halte ja nichts von langen Ausdrücken in Format-Strings. Zumindest das `str` ist überflüssig.
Ich halte ja nichts von langen Ausdrücken in Format-Strings. Zumindest das `str` ist überflüssig.
Statt mit randint rumzufummeln kannst du einfach “random() > .5” machen. Dann begehst du einen klassischen Fehler, wenn du die verflossene Zeit nicht bestimmst (so wie ich das getan habe), sondern ANNIMMST, dass sie deinem angegebenen interval entsprechend verflossen ist. Das ist aber niemals garantiert. Deine Uhr geht also zu langsam.
Last but not least ist die große, vertane Chance nicht zu sehen, dass eigentlich schon fast alles vorhanden ist, um so ein Verhalten generisch einzubauen.
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.
Last but not least ist die große, vertane Chance nicht zu sehen, dass eigentlich schon fast alles vorhanden ist, um so ein Verhalten generisch einzubauen.
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.
-
- User
- Beiträge: 407
- Registriert: Freitag 6. August 2010, 17:03
@Sirius3: Ich habe den Code jetzt nach deinen Vorschlägen optimiert.
@__deets__: Das sind einige Vorschläge. Soweit ich die Vorschläge verstanden habe, habe ich den Code jetzt umgebaut.
Bin ich mit dem Codegerüst unten auf dem richtigen Weg?
Gruß
Atalanttore
@__deets__: Das sind einige Vorschläge. Soweit ich die Vorschläge verstanden habe, habe ich den Code jetzt umgebaut.
Bin ich mit dem Codegerüst unten auf dem richtigen Weg?
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 = 5000
UPDATE_INTERVAL = 100
SPEED = 5000
MIN_DISTANCE = 30000
DISTANCE_VARIANCE = 20000
D = 1000
class Countdown:
def __init__(self, scene, remaining_time=START_COUNTDOWN):
self.remaining_time = remaining_time
self.painterpath = QPainterPath()
scene.addText(f"In {self.remaining_time} Sekunden gehts los!")
self._path = scene.addPath(self.painterpath)
def update(self, elapsed_time):
self.remaining_time = (START_COUNTDOWN - elapsed_time) / 1000
#Zeit aktualisieren
if elapsed_time >= START_COUNTDOWN:
self._obsolet = True
else:
self._obsolet = False
return self._obsolet
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)
self._things_on_screen = list()
self._things_on_screen.append(self.countdown)
self._timer = QTimer(self)
self._timer.setInterval(100)
self._timer.timeout.connect(self._update_all)
self._timer.start()
def spawn_stars(self):
self._things = []
for i in range(100):
self._things.append(Star(self.scene))
def _update_all(self):
now = time.monotonic()
elapsed = now - self._last
self._last = now
for thing in self._things_on_screen:
if thing.update(elapsed):
# Countdown-Objekt entfernen und umschalten auf Sterne
pass
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
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.
-
- User
- Beiträge: 407
- Registriert: Freitag 6. August 2010, 17:03
@__deets__: Danke. Dann kann ich weitermachen.
Gruß
Atalanttore
Gruß
Atalanttore
@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.
Es gibt ein _things_on_screen und ein _things, wobei zweiteres gar nicht benutzt wird.
-
- 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