Formen

Rect, Triangle, Circle, Line und Polyline sind Form-Komponenten. Du gibst einer Entität beim Erzeugen eine davon mit, und die Entität zeichnet diese Form in jedem Frame, bis du sie zerstörst.

from pyxen import *

def start():
    world.spawn(x=110, y=90, shape=Rect(48, 32, fill=(0.4, 0.7, 1.0, 1.0)))
    world.spawn(x=210, y=90, shape=Circle(24, stroke=(1.0, 0.6, 0.3, 1.0), stroke_width=2))

Eine Form ist eine echte Entität, also gilt alles, was Entitäten können: bewege sie mit entity.x und entity.y, drehe sie mit entity.rotation, gib ihr ein Elternobjekt, animiere sie per Tween, leg sie auf eine Ebene, blende sie mit entity.visible aus.


Formen oder draw?

Pyxen bietet dieselben fünf Primitive zweimal an. Auf dem Bildschirm sind sie identisch und unterscheiden sich nur in ihrer Lebensdauer. Die andere Hälfte steht unter draw.

Form-Komponentedraw
LebensdauerLebt, bis du die Entität zerstörstEin Frame
PositionPosition, Rotation und Skalierung der EntitätWeltkoordinaten, die du übergibst
KostenEine vollständige Entität: Transform, Ebene, KomponentenspeicherEin paar Quads im Frame-Puffer
Gut fürSpielobjekte — Schiffe, Wände, feste OberflächeProzedurale Geometrie, Debug-Anzeigen, Balken, Spuren

Nimm eine Form, wenn das Ding etwas in deinem Spiel ist. Nimm draw, wenn du ein Bild von etwas malst, das du ohnehin schon selbst verwaltest.


Drei Stolperfallen

Eine Form wird beim Erzeugen festgelegt und lässt sich danach nicht mehr ändern. Es gibt kein entity.shape — der Zugriff wirft ValueError: component schema does not exist. Anders als sprite und text, die sehr wohl zuweisbar sind. Um eine Form zu ändern, zerstöre die Entität und erzeuge eine neue.

entity.color färbt eine Form nicht ein. Bei Sprites funktioniert es; eine Form zeichnet mit ihrem eigenen fill und stroke und ignoriert es. Setze die Farbe an der Form selbst.

Die Umrissstärke hat zwei Namen. Rect, Triangle und Circle nehmen stroke_width; Line und Polyline nehmen width. Gemeint ist dasselbe.


Farben

Jedes Farbargument nimmt ein (r, g, b, a)-Tupel mit Komponenten von 0.0 bis 1.0 oder ein Color.

Rect(40, 20, fill=(1, 0, 0, 1))        # deckendes Rot
Rect(40, 20, fill=(1, 0, 0, 0.35))     # Rot mit 35 %

Eine Form ohne fill und ohne stroke zeichnet nichts. Beide starten vollständig transparent, Rect(40, 40) ist also eine gültige, unsichtbare Entität — die häufigste Überraschung hier. Du kannst beide angeben und bekommst eine gefüllte Form mit Umriss.


Typen

Rect(width, height, fill=None, stroke=None, stroke_width=1, pivot=None)

Ein Rechteck.

ArgumentTypBeschreibung
width, heightfloatGröße in Pixeln.
fillTupelInnenfarbe. Weglassen für keine Füllung.
strokeTupelUmrissfarbe. Weglassen für keinen Umriss.
stroke_widthfloatStärke des Umrisses. Standard ist 1.
pivotTupelDer Punkt, auf den sich die Position der Entität bezieht, in Form-Pixeln. Standard ist die Ecke; (width/2, height/2) zentriert sie.

Rect ist die einzige Form mit pivot — und das ist zugleich der Punkt, um den sich die Rotation dreht.

def start():
    world.spawn(x=110, y=90, shape=Rect(48, 32, fill=(0.4, 0.7, 1.0, 1.0)))
    # Auf der eigenen Position zentriert, dreht sich also auf der Stelle.
    world.spawn(name="spinner", x=210, y=90,
                shape=Rect(48, 32, stroke=(1.0, 0.6, 0.3, 1.0), stroke_width=2,
                           pivot=(24, 16)))

def update():
    world.get(name="spinner").rotation += 0.02

Triangle(x1, y1, x2, y2, x3, y3, fill=None, stroke=None, stroke_width=1)

Ein Dreieck durch drei Punkte, angegeben relativ zur Position der Entität.

ArgumentTypBeschreibung
x1, y1, x2, y2, x3, y3floatDie drei Ecken, als Versatz zur Entität.
fillTupelInnenfarbe. Weglassen für keine Füllung.
strokeTupelUmrissfarbe. Weglassen für keinen Umriss.
stroke_widthfloatStärke des Umrisses.
world.spawn(x=160, y=90, shape=Triangle(0, -20, 18, 14, -18, 14, fill=(1, 1, 0.3, 1)))

Circle(radius, fill=None, stroke=None, stroke_width=1, segments=12)

Ein Kreis, zentriert auf der Entität.

ArgumentTypBeschreibung
radiusfloatRadius in Pixeln.
fillTupelInnenfarbe. Weglassen für keine Füllung.
strokeTupelUmrissfarbe. Weglassen für keinen Umriss.
stroke_widthfloatStärke des Umrisses.
segmentsintGerade Segmente, die die Kurve annähern. Standard ist 12.

Ein Kreis ist in Wahrheit ein Vieleck. Zwölf Segmente wirken bei kleinen Größen rund; erhöhe den Wert für große Kreise und senke ihn bewusst, wenn du ein sichtbares Vieleck willst — segments=6 ist ein Sechseck.

world.spawn(x=110, y=90, shape=Circle(28, fill=(0.5, 1.0, 0.7, 1.0), segments=6))
world.spawn(x=210, y=90, shape=Circle(28, fill=(1.0, 0.5, 0.7, 1.0), segments=32))

Line(x1, y1, x2, y2, stroke=None, width=1)

Eine gerade Linie zwischen zwei Punkten, relativ zur Entität.

ArgumentTypBeschreibung
x1, y1floatAnfang, als Versatz zur Entität.
x2, y2floatEnde.
strokeTupelLinienfarbe.
widthfloatLinienstärke. Achtung: width, nicht stroke_width.
world.spawn(x=60, y=90, shape=Line(0, 0, 80, -40, stroke=(0.3, 1, 0.5, 1), width=2))

Polyline(points, stroke=None, width=1, closed=False, fill=None)

Eine Folge verbundener Segmente.

ArgumentTypBeschreibung
pointsTupel oder FloatBufferFlache Folge (x1, y1, x2, y2, …) — keine Liste von Paaren.
strokeTupelLinienfarbe.
widthfloatLinienstärke. Achtung: width, nicht stroke_width.
closedboolBei True wird der letzte Punkt mit dem ersten verbunden.
fillTupelFüllt die eingeschlossene Fläche. Vor allem mit closed=True sinnvoll.

Die Punkte liegen bewusst flach: genau diese Form hat ein FloatBuffer bereits, sodass ein einmal berechneter Pfad unverändert an Polyline übergeben werden kann.

world.spawn(x=250, y=90,
            shape=Polyline((0, 0, 30, -25, 60, 0), stroke=(1, 0.9, 0.2, 1), width=3))