Pyxen Script

Pyxen-Spiele werden in Pyxen Script geschrieben, einem für Spiele gemachten Python-Dialekt. Er hat die Syntax und das Gefühl von Python: Einrückung, def, Listen, Dictionaries, f-Strings, Comprehensions. Was sich ändert, liegt darunter. Dein Code wird als Ganzes geprüft und kompiliert, bevor das Spiel startet, und er läuft auf einer Laufzeitumgebung, die für genau eine Aufgabe gebaut ist: ein Spiel auszuführen, und zwar auf jedem Gerät gleich.

Wenn du Python schon kennst, funktioniert das meiste, was du schreibst, wie erwartet. Diese Seite erklärt, warum es Pyxen Script gibt, was es mit Python teilt und was es bewusst weglässt.

Warum ein Dialekt

Ein Spiel verlangt von einer Sprache andere Dinge als ein Skript.

Frühere Versionen von Pyxen nutzten einen MicroPython-Interpreter; Pyxen Script ersetzt ihn.

Was wie in Python ist

Pyxen Script wird gegen Hunderte einzeiliger Python-Idiome getestet, jedes verglichen mit der Ausgabe von Standard-Python. Alles Folgende verhält sich wie in Python:

Was anders ist

Keine Klassen

Pyxen Script hat kein class. Das ist so gewollt.

In einem Pyxen-Spiel liegt der Zustand auf Entitäten und ihren Komponenten, die der Engine gehören, von ihr kompakt gespeichert und dir im Frame-Inspektor gezeigt werden. Eine Klasse wäre ein zweiter Ort für denselben Zustand, unsichtbar für die Engine und für den Inspektor. Wo Python-Code also eine Klasse definieren würde, erzeugt Pyxen-Code eine Entität, hängt eine Komponente an und schreibt Funktionen, die damit arbeiten.

Eine typische Python-Klasse:

class Enemy:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.hp = 3

    def hit(self):
        self.hp -= 1

Dasselbe in Pyxen Script:

def spawn_enemy(x, y):
    e = world.spawn(name="enemy", x=x, y=y, sprite=Sprite("slime"))
    e.enemy = {"hp": 3, "speed": 1.5}
    return e

def hit(e):
    e.enemy.hp -= 1
    if e.enemy.hp <= 0:
        world.destroy(e)

def update():
    for e in world.all("enemy"):
        e.x -= e.enemy.speed

Die Entität hat bereits eine Position, ein Sprite und einen Platz in der Szene. Die Komponente enemy enthält, was für Gegner spezifisch ist, und world.all("enemy") findet sie alle, sodass es keine Liste von Instanzen gibt, die synchron gehalten werden muss. Für einfache Daten, die keine Spielobjekte sind, etwa eine Highscore-Tabelle oder eine Levelbeschreibung, nimm Dictionaries und Listen. Das vollständige Modell findest du unter Entitäten & Komponenten.

Keine Exceptions

Pyxen Script hat kein try, except, finally oder raise. Auch das ist so gewollt.

Ein Fehler in Pyxen Script hält das Spiel an und wird an seiner Zeile gemeldet, zusammen mit den Aufrufen, die dorthin geführt haben. Nichts kann ihn unterwegs abfangen, also wird ein Fehler nie von einem except: pass verschluckt und zu einem Spiel, das sich drei Level später still falsch verhält.

Die meisten try-Blöcke in Spielcode stellen eine Frage, auf die es eine direkte Antwort gibt. Stell stattdessen die Frage:

# hat diese Entität eine Komponente?
hp = e.health.value if e.has("health") else 0

# ist dieser Schlüssel im Dictionary?
best = scores.get(name, 0)

# ist dieser Text eine Zahl?
if text.isdigit():
    level = int(text)

# liegt dieser Index in der Liste?
if i < len(items):
    item = items[i]

assert ist verfügbar und hält das Spiel an seiner Zeile an, wenn seine Bedingung falsch ist.

Ebenfalls nicht in Pyxen Script

Jeder dieser Punkte wird mit Namen und Zeile gemeldet, bevor das Spiel startet.

Module und Imports

Ein Projekt wird als ein Programm kompiliert. Jedes Skript darin ist für die anderen bereits unter seinem Dateinamen sichtbar: Eine Funktion spawn() in monsters.py wird aus jedem anderen Skript als monsters.spawn() aufgerufen, ohne Import. Die Engine-API (world, input, Sprite, …) ist ebenfalls immer verfügbar.

Imports, die du schon hast, etwa from pyxen import * oder import monsters, funktionieren weiterhin. Pyxen Script behandelt sie als unnötig, und du kannst sie entfernen.