Game Loop
Pyxen führt dein Spiel mit festen 30 Frames pro Sekunde aus. Du schreibst keine Hauptschleife — du definierst zwei Funktionen und Pyxen ruft sie für dich auf.
start() und update()
def start():
# wird einmal aufgerufen, am Anfang
pass
def update():
# wird jeden Frame aufgerufen, 30 Mal pro Sekunde
pass
start() ist, wo du deine Welt einrichtest: Entitäten erzeugen, platzieren, die Szene konfigurieren.
update() ist, wo deine Spiellogik läuft: Eingaben lesen, Entitäten bewegen, Bedingungen prüfen, Zustand aktualisieren. Nach jedem Aufruf von update() rendert die Engine den Frame.
Feste Bildrate
Jeder Frame dauert genau gleich lang: 1/30 Sekunde (~33ms). Das bedeutet:
- Spiellogik ist deterministisch — gleiche Eingaben erzeugen gleiche Ergebnisse
- Du kannst Frames direkt zählen (
time.frame) statt mit variablen Delta-Zeiten zu arbeiten - Der Zeitschieber und Frame-Inspektor funktionieren zuverlässig, weil jeder Frame ein sauberer Schnappschuss ist
Du kannst immer noch time.dt (Delta-Zeit) verwenden, wenn du zeitbasierte Bewegung bevorzugst, aber es wird immer 1/30 sein.
Frame-Reihenfolge
Jeden Frame führt Pyxen diese Schritte in dieser Reihenfolge aus:
- Eingabe — lies Tastatur-, Maus-, Touch- und Gamepad-Zustand
- update() — dein Code wird ausgeführt
- Kollision — die Engine löst Grid-Bodies auf und ruft Kollisions-Callbacks auf
- Rendern — die Engine zeichnet alle sichtbaren Entitäten
Das bedeutet, wenn du Eingaben in update() liest, sind die Werte aktuell. Wenn du eine Entität bewegst, passiert die Kollisionsauflösung direkt danach.
Kollisions-Callbacks
Wenn du on_collision oder on_tile_collision auf oberster Ebene definierst, ruft die Engine sie während des Kollisionsschritts auf:
def on_collision(entity, other):
# zwei Grid-Bodies haben sich überlappt
pass
def on_tile_collision(entity, tile):
# ein Grid-Body hat eine Tilemap-Kachel getroffen
pass
Diese laufen nach update(), aber vor dem Rendern.
Timing
Das pyxen.time-Modul gibt dir:
| Eigenschaft | Wert | Verwendung |
|---|---|---|
time.frame | 0, 1, 2, ... | Frame-Zähler seit Start |
time.dt | 0.0333... | Delta-Zeit (immer 1/30) |
time.t | 0.0, 0.033, 0.066, ... | Vergangene Zeit in Sekunden |
time.fps | 30 | Frames pro Sekunde |
Für zeitgesteuerte Ereignisse kannst du beide Ansätze verwenden:
# Frame-basiert: alle 60 Frames feuern (2 Sekunden)
if time.frame % 60 == 0:
fire()
# Zeit-basiert: dasselbe
if int(time.t * 30) % 60 == 0:
fire()
Frame-Zählung ist normalerweise einfacher und vorhersehbarer.