pyxen.Mesh
Ein Mesh ist ein zeichenbares Objekt aus Dreiecken oder Quads, die Sie Eckpunkt für Eckpunkt definieren. Jeder Eckpunkt hat eine Position, einen Punkt eines Bildes, der abgetastet wird, und eine Farbe. Ein Mesh kann daher ein einfarbiges Polygon sein, ein texturierter Streifen, ein Farbverlauf oder eine Fläche, die sich biegt und wellt.
Es ist eine Komponente wie Sprite, Text und die Formen: Sie geben einer Entity beim Spawnen eines mit, und die Entity zeichnet es in jedem Frame.
Ein Mesh ist 2D. Eckpunkte haben ein x und ein y und keine Tiefe: kein z, keinen Tiefenpuffer, keine Perspektive. Alles wird auf der CPU transformiert und flach gezeichnet, wie jede andere Komponente in der Reihenfolge der Ebenen. Es dient dazu, Formen zu zeichnen, die Sprites und Formen nicht können, und ist keine 3D-Engine.
from pyxen import *
def start():
world.spawn(x=160, y=60,
mesh=Mesh(points=(-40, 40, 0, -40, 40, 40), fill=(0.3, 0.9, 0.5, 1.0)))
Konstruktor
Mesh(
*,
points=None,
vertices=None,
indices=None,
fill=None,
image=None,
mode=TRIANGLES
)
Alle Parameter sind reine Schlüsselwortargumente. Sie müssen points oder vertices angeben; fehlen beide, wird ValueError: Mesh needs points= or vertices= ausgelöst.
Parameter
| Parameter | Typ | Beschreibung |
|---|---|---|
points | Tupel, Liste oder FloatBuffer | Flache Positionen, zwei Floats pro Eckpunkt: (x1, y1, x2, y2, …). Jeder Eckpunkt erhält die eine Farbe aus fill |
vertices | Tupel, Liste oder FloatBuffer | Vollständige Eckpunkte, acht Floats pro Eckpunkt: x, y, u, v, r, g, b, a |
indices | Tupel, Liste oder IntBuffer | Welche Eckpunkte jedes Dreieck oder Quad bilden. Weglassen, um die Eckpunkte der Reihe nach zu verwenden |
fill | Farbe | Farbe für points. Ein Tupel (r, g, b, a) oder eine Color. Standard: undurchsichtiges Weiß |
image | str | Name eines Bild-Assets, mit dem das Mesh texturiert wird |
mode | TRIANGLES oder QUADS | Was jede Grundform ist. Standard TRIANGLES |
Übergeben Sie sowohl points als auch vertices, gilt vertices.
Eckpunkte
Ein Eckpunkt besteht aus acht Zahlen:
| Float | Bedeutung |
|---|---|
x, y | Position in Pixeln, relativ zur Entity. Siehe Transformationen |
u, v | Welchen Punkt des Bildes dieser Eckpunkt zeigt, von 0.0 bis 1.0 über das ganze Bild. (0, 0) ist die obere linke Ecke, (1, 1) die untere rechte. Ohne image ohne Wirkung |
r, g, b, a | Die Eckpunktfarbe, je 0.0 bis 1.0 |
Die Farbe wird zwischen den Eckpunkten interpoliert: Gibt man jeder Ecke eines Quads eine andere Farbe, entsteht ein Verlauf. Mit einem image wird die Farbe mit dem Bild multipliziert: Weiß lässt das Bild unverändert, Rot behält nur seinen Rotanteil.
Die kurze Form points ist für Geometrie ohne Textur und mit einer Farbe gedacht. Sie entspricht vertices mit u = v = 0 und allen Farben gleich fill.
Indizes
Eckpunkte werden nur einmal aufgelistet. Die Indizes legen fest, welche davon jede Grundform bilden, sodass zwei Dreiecke mit einer gemeinsamen Kante deren zwei Eckpunkte teilen können, statt sie zu wiederholen.
- Bei
mode=TRIANGLESbilden je 3 Indizes ein Dreieck. - Bei
mode=QUADSbilden je 4 Indizes ein Quad. Listen Sie die Ecken der Reihe nach um das Quad herum auf, zum Beispiel oben links, oben rechts, unten rechts, unten links.
Ein Index zählt Eckpunkte, nicht Floats: 2 bedeutet den dritten Eckpunkt, egal in welcher Eckpunktform.
# Ein Quadrat aus zwei Dreiecken, die sich die Kante 0-2 teilen
Mesh(vertices=square, indices=(0, 1, 2, 0, 2, 3))
# Dasselbe Quadrat als ein Quad
Mesh(vertices=square, indices=(0, 1, 2, 3), mode=QUADS)
Ohne indices werden die Eckpunkte der Reihe nach verwendet: die ersten drei sind ein Dreieck (oder die ersten vier ein Quad) und so weiter. Das passt für einen Streifen, der Eckpunkt für Eckpunkt aufgebaut wird.
Zwei Dinge werden toleriert statt gemeldet. Ein Index hinter dem letzten Eckpunkt lässt sein eigenes Dreieck oder Quad weg, der Rest wird weiter gezeichnet. Eine abschließende Gruppe aus weniger als 3 (bzw. 4) Indizes wird ignoriert. Ein Spiel, das seine Eckpunkte neu aufbaut, während noch eine alte Indexliste gilt, verliert für einen Frame ein Dreieck, statt abzubrechen.
Texturen
world.spawn(x=40, y=40, mesh=Mesh(
vertices=(0, 0, 0, 0, 1, 1, 1, 1,
64, 0, 1, 0, 1, 1, 1, 1,
64, 64, 1, 1, 1, 1, 1, 1,
0, 64, 0, 1, 1, 1, 1, 1),
image="tiles",
mode=QUADS,
))
image ist der Name eines Bild-Assets im Projekt, ohne Endung, gefunden wie bei Sprite. u und v jedes Eckpunkts wählen den Punkt des Bildes, den er zeigt, und das Bild wird dazwischen über die Grundform gestreckt.
u und v sind Bruchteile des ganzen Bildes, keine Pixel. Werte außerhalb von 0.0–1.0 kacheln nicht: Sie wiederholen die Randpixel des Bildes.
Transformationen
Die Eckpunkte eines Mesh sind lokal zu seiner Entity. x, y, rotation und scale der Entity, und die ihres Parents, falls vorhanden, werden beim Zeichnen auf jeden Eckpunkt angewendet. Bauen Sie die Geometrie um (0, 0) herum auf und bewegen, drehen und skalieren Sie sie wie jede andere Entity.
def start():
world.spawn(name="diamond", x=160, y=90,
mesh=Mesh(points=(-20, 0, 0, -30, 20, 0, 0, 30),
indices=(0, 1, 2, 3), mode=QUADS,
fill=(1.0, 0.6, 0.2, 1.0)))
def update():
world.get(name="diamond").rotation += 1
Ein Mesh anhängen
Beim Spawnen:
e = world.spawn(x=100, y=60, mesh=Mesh(points=(0, 0, 50, 0, 25, 40)))
Das Spawnen kopiert die Geometrie in die Entity. Danach ist das Mesh-Objekt, das Sie gebaut haben, nicht mehr mit ihr verbunden: Ändern Sie die lebende Geometrie über entity.mesh, wie unten beschrieben. Dasselbe Mesh-Objekt kann mehrfach gespawnt werden, um unabhängige Kopien zu erzeugen.
Komponenteneigenschaften
Zugriff über entity.mesh:
| Mitglied | L/S | Beschreibung |
|---|---|---|
points = buf | S | Ersetzt die Position jedes Eckpunkts aus einer flachen Folge (x, y, …). Farben und u, v bleiben unverändert |
vertices = buf | S | Ersetzt jeden Eckpunkt aus einer flachen Folge, je 8 Floats. Die Anzahl der Eckpunkte kann sich ändern |
indices = buf | S | Ersetzt die Indexliste |
visible = flag | S | Zeichnet das Mesh oder überspringt es, ohne die Geometrie zu verlieren |
mode | L | TRIANGLES oder QUADS |
set_vertex(i, x, y, [u, v, [r, g, b, a]]) | Ändert einen einzelnen Eckpunkt. u, v und danach r, g, b, a sind optional, aber jede Gruppe muss vollständig angegeben werden | |
count() | Die Anzahl der Eckpunkte |
mode steht beim Erstellen des Mesh fest.
e.mesh.points = (0, 0, 60, 0, 30, 50) # die drei Eckpunkte verschieben
e.mesh.set_vertex(1, 70, 5) # dann einen davon anstupsen
e.mesh.visible = False # ausblenden
set_vertex mit einer Eckpunktnummer, die nicht existiert, ist ein Fehler, ebenso entity.mesh bei einer Entity ohne Mesh.
Ein points, das kürzer als das Mesh ist, bewegt nur die ersten Eckpunkte; ein längeres wird am letzten Eckpunkt abgeschnitten.
Ein Mesh animieren
Verwenden Sie points für alles, was sich in jedem Frame bewegt. Eine einzige Zuweisung ersetzt alle Positionen, und die Schleife über die Eckpunkte läuft in der Engine; dieselbe Aktualisierung über set_vertex kostet einen Python-Aufruf pro Eckpunkt.
Das macht das Hochladen billig, nicht das Berechnen. Jede neue Position berechnen Sie weiterhin selbst in jedem Frame, und bei einem Mesh mit vielen Eckpunkten geht dort die Zeit hin. Ein Mesh, das einmal gebaut und dann mit seiner Entity bewegt, gedreht oder skaliert wird, kostet pro Frame fast nichts; ein Mesh, dessen Form sich in jedem Frame ändert, kostet so viel, wie die Berechnung seiner Geometrie kostet.
Dieses wogende Band baut seine Geometrie einmal auf und bewegt die Eckpunkte danach über entity.mesh.points. Es braucht keine Assets.
from pyxen import *
import math
COLUMNS = 20
STEP = 14
def ribbon(t):
points = []
for i in range(COLUMNS + 1):
y = math.sin(t * 0.08 + i * 0.5) * 14
points.append(i * STEP)
points.append(y - 12) # obere Kante
points.append(i * STEP)
points.append(y + 12) # untere Kante
return points
def start():
vertices = []
indices = []
for i in range(COLUMNS + 1):
r = i / COLUMNS
# oberer und unterer Eckpunkt dieser Spalte: x, y, u, v, r, g, b, a
vertices.extend((0, 0, 0, 0, 1.0 - r, 0.4, r, 1.0))
vertices.extend((0, 0, 0, 0, 1.0 - r, 0.4, r, 1.0))
for i in range(COLUMNS):
a = i * 2
# oben links, unten links, unten rechts, oben rechts
indices.extend((a, a + 1, a + 3, a + 2))
world.spawn(name="ribbon", x=20, y=90,
mesh=Mesh(vertices=vertices, indices=indices, mode=QUADS))
def update():
world.get(name="ribbon").mesh.points = ribbon(time.frame)
FloatBuffer und IntBuffer
Ein Mesh lässt sich aus Tupeln und Listen bauen, aber bei großer oder oft neu aufgebauter Geometrie ersparen zwei Puffertypen, jedes Mal ein neues Tupel zu erzeugen.
positions = FloatBuffer(64) # Kapazität
positions.append(0.0)
positions.append(0.0)
positions[0] = 5.0 # Lesen und Schreiben per Index
indices = IntBuffer(64)
indices.append(0)
FloatBuffer(capacity) | IntBuffer(capacity) | |
|---|---|---|
| Enthält | Floats: Positionen, Eckpunkte | Ganzzahlen: Indizes |
append(value) | Hängt hinten an | Hängt hinten an |
clear() | Leert ihn | Leert ihn |
buf[i] | Lesen und Zuweisen | Lesen und Zuweisen |
capacity ist die Startgröße; ein Puffer wächst, wenn Sie darüber hinaus anhängen. Übergeben Sie sie als points=, vertices= oder indices= an ein Mesh, oder weisen Sie sie der passenden Eigenschaft von entity.mesh zu.
Mesh oder Formen?
Formen sind das richtige Werkzeug für ein Rechteck, einen Kreis, ein Dreieck oder eine Linie, und draw für ein Bild, das Sie in jedem Frame neu zeichnen. Greifen Sie zum Mesh, wenn Sie brauchen, was diese nicht können: ein texturiertes Polygon, das kein Rechteck ist, eine andere Farbe an jeder Ecke oder eine Fläche, deren Eckpunkte sich unabhängig bewegen, etwa eine Fahne, eine Straße, ein Band oder eine Wasseroberfläche.
Häufige Fehler
Fehlende Geometrie
Mesh(fill=(1, 0, 0, 1))
ValueError: Mesh needs points= or vertices=