Shapes

Rect, Triangle, Circle, Line and Polyline are shape components. You give one to an entity when you spawn it, and the entity draws that shape every frame until you destroy it.

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))

A shape is a real entity, so everything entities can do applies: move it with entity.x and entity.y, spin it with entity.rotation, parent it, tween it, put it on a layer, hide it with entity.visible.


Shapes or draw?

Pyxen gives you the same five primitives twice. They look identical on screen and differ only in lifetime. See draw for the other half.

Shape componentdraw
LifetimeLives until you destroy the entityOne frame
PositionEntity position, rotation, scaleWorld coordinates you pass in
CostA full entity: transform, layer, component storageA few quads in the frame buffer
Good forGame objects — ships, walls, UI that stays putProcedural geometry, debug overlays, HUD bars, trails

Reach for a shape when the thing is something in your game. Reach for draw when you are painting a picture of something you already track yourself.


Three things that catch people out

A shape is set at spawn and cannot be changed afterwards. There is no entity.shape — reading it raises ValueError: component schema does not exist. This is unlike sprite and text, which are assignable. To change a shape, destroy the entity and spawn a new one.

entity.color does not tint a shape. It works for sprites; a shape draws in its own fill and stroke and ignores it. Set the colour on the shape itself.

The stroke width argument has two names. Rect, Triangle and Circle take stroke_width; Line and Polyline take width. They mean the same thing.


Colours

Every colour argument takes an (r, g, b, a) tuple with components from 0.0 to 1.0, or a Color.

Rect(40, 20, fill=(1, 0, 0, 1))        # opaque red
Rect(40, 20, fill=(1, 0, 0, 0.35))     # 35% red

A shape with neither fill nor stroke draws nothing. Both start fully transparent, so Rect(40, 40) is a valid, invisible entity — the most common surprise here. You can pass both to get a filled shape with an outline.


Types

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

A rectangle.

ArgumentTypeDescription
width, heightfloatSize in pixels.
filltupleInterior colour. Omit for no fill.
stroketupleOutline colour. Omit for no outline.
stroke_widthfloatOutline thickness. Defaults to 1.
pivottupleThe point the entity’s position refers to, in shape pixels. Defaults to the corner; (width/2, height/2) centres it.

Rect is the only shape with a pivot — it is also what rotation turns around.

def start():
    world.spawn(x=110, y=90, shape=Rect(48, 32, fill=(0.4, 0.7, 1.0, 1.0)))
    # Centred on its own position, so rotation spins in place.
    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)

A triangle through three points, given relative to the entity’s position.

ArgumentTypeDescription
x1, y1, x2, y2, x3, y3floatThe three corners, as offsets from the entity.
filltupleInterior colour. Omit for no fill.
stroketupleOutline colour. Omit for no outline.
stroke_widthfloatOutline thickness.
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)

A circle centred on the entity.

ArgumentTypeDescription
radiusfloatRadius in pixels.
filltupleInterior colour. Omit for no fill.
stroketupleOutline colour. Omit for no outline.
stroke_widthfloatOutline thickness.
segmentsintStraight segments approximating the curve. Defaults to 12.

A circle is really a polygon. Twelve segments reads as round at small sizes; raise it for large circles, and lower it on purpose when you want a visible polygon — segments=6 is a hexagon.

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)

A straight line between two points, relative to the entity.

ArgumentTypeDescription
x1, y1floatStart, as an offset from the entity.
x2, y2floatEnd.
stroketupleLine colour.
widthfloatLine thickness. Note: width, not 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)

A run of connected segments.

ArgumentTypeDescription
pointstuple or FloatBufferFlat sequence (x1, y1, x2, y2, …) — not a list of pairs.
stroketupleLine colour.
widthfloatLine thickness. Note: width, not stroke_width.
closedboolWhen True, joins the last point back to the first.
filltupleFills the enclosed area. Most useful with closed=True.

Points are flat on purpose: it is the shape a FloatBuffer already has, so a path computed once can be handed straight to Polyline without rebuilding it.

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