Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Shapes that change

A sprite is a picture, and a picture is always a rectangle. A polygon can be any shape at all: a ship, a rock, a beam of light, a blob of jelly. This lesson is about polygons that turn, grow, wear a picture, and change shape point by point while the game runs.

Turning and growing

You met POLYGON in Drawing on the screen: a list of points, an edge color and a fill color. After !! it can be turned, scaled and moved first, without your having to work out where any of the points go.

SCREEN 320, 240
ship = [(0, -14), (-9, 10), (0, 5), (9, 10)]
angle = 0
DO
    IF COOLDOWN(1)
        angle = angle + 2
        MEMORYBUFFER "scene"
            POLYGON ship, 15, 9 !! offset((60, 120))
            POLYGON ship, 15, 10 !! rotate(angle) rotate_origin((0, 0)) offset((160, 120))
            POLYGON ship, 15, 12 !! scale((2, 2)) rotate(-angle) rotate_origin((0, 0)) offset((260, 120))
        END MEMORYBUFFER
        FRAME
            CLS
            PUTIMAGE (0, 0), "scene"
        END FRAME
    END IF
LOOP

The ship’s points are written around (0, 0), its nose at the top. offset moves the finished shape, and it’s always done last, so it doesn’t matter where in the !! you put it. rotate turns it, in degrees, about its center unless rotate_origin names another point, and scale makes it bigger or smaller: (2, 2) is twice the size both ways.

A picture on a shape

A polygon can wear an image, too, stretched across it. Give POLYGON an image after its fill, and !! uv says which part of the image goes at each point: (0, 0) is the image’s top left corner and (1, 1) its bottom right. An edge color of -1 means no edge.

SCREEN 320, 240
LOADIMG "city.png", "city"
corners = [(0, 0), (1, 0), (1, 1), (0, 1)]
t = 0
DO
    IF COOLDOWN(1)
        t = t + 0.05
        lean = sin(t) * 40
        quad = [(80 + lean, 30), (240 + lean, 30), (260, 210), (60, 210)]
        FRAME
            CLS
            POLYGON quad, -1, 15, "city" !! uv(corners)
        END FRAME
    END IF
LOOP

The top of the city sways from side to side while the bottom stays put, and the picture bends to fit.

That POLYGON goes straight into the FRAME, without a MEMORYBUFFER. A buffer builds its picture one pixel at a time, and stretching an image across a shape is a job for the graphics chip, so a buffer can’t do it:

SCREEN 320, 240
LOADIMG "city.png", "city"
MEMORYBUFFER "scene"
    POLYGON [(0, 0), (100, 0), (100, 100)], -1, 15, "city" !! uv([(0, 0), (1, 0), (1, 1)])
END MEMORYBUFFER

Shapes I keep

Everything above is drawn again every frame by your loop. A sprite, though, is something I draw myself, and a polygon can be one of those too.

For that, its points go in a MUTATION: a list of points that you and I share. SET POLYGON binds it to a polygon number, and from then on I draw it every frame, the way I draw sprites. Its modifiers are a lot like a sprite’s: location and origin place it, fill and color color it, texture and uv put a picture on it, and behavior moves it.

SCREEN 320, 240
LOADIMG "city.png", "city"
poster = MUTATION((0, 0), (100, 0), (100, 100), (0, 100))
corners = MUTATION((0, 0), (1, 0), (1, 1), (0, 1))
SET POLYGON 0 !! bind(poster) texture("city") uv(corners) location((160, 120)) origin((0.5, 0.5))

BEHAVIOR "spin"
    ROTATE 360, 240 !! loop
END BEHAVIOR
SET POLYGON 0 !! behavior("spin")

shown = TRUE
DO
    IF COOLDOWN(60)
        shown = NOT shown
        IF shown THEN SHOW POLYGON 0 ELSE HIDE POLYGON 0
    END IF
LOOP

The picture spins by itself, and the loop does nothing but blink it once a second with HIDE POLYGON and SHOW POLYGON. DESTROY POLYGON gets rid of it: its shape on the screen, and its behaviors. The MUTATION it was bound to is still yours afterwards, with all its points.

Crash-tan’s Tip: SET POLYGON takes solid, tag and velocity too, so a polygon I keep can bump into things the way a sprite does.

Changing a shape

The points in a MUTATION are shared: when you change one, the shape on the screen changes with it. Here’s a blob of jelly, twelve points around a circle, each pushed in and out a little every tick:

SCREEN 320, 240
blob = MUTATION()
FOR i = 0 ... 11
    a = i * PI / 6
    SET MUTATIONPOINT blob, i, (160 + cos(a) * 50, 120 + sin(a) * 50)
NEXT
rest = clone(blob)
SET POLYGON 0 !! bind(blob) color(15) fill(10)

t = 0
DO
    IF COOLDOWN(1)
        t = t + 0.1
        FOR i = 0 ... 11
            wobble = 1 + sin(t + i * 2) * 0.08
            SET MUTATIONPOINT blob, i, (160 + (rest(i).x - 160) * wobble, 120 + (rest(i).y - 120) * wobble)
        NEXT
    END IF
LOOP

SET MUTATIONPOINT moves a point, and given a number one past the end, it adds one there, which is how the blob gets its twelve. Adding points is for a shape that isn’t bound yet: once a polygon is drawing it, its number of points is fixed, so what I draw is never in two minds. Build the shape first, then bind it. blob(i) = (x, y) moves a point too, and blob(i) reads one.

clone makes a copy that has nothing to do with the original any more, so rest remembers where every point started out while blob wobbles. The copy is a plain list: it isn’t bound to anything, and nobody draws it.

To ask about a shape, mutationcount is how many points it has, and mutationpointx and mutationpointy are where one of them is:

SCREEN 320, 240
triangle = MUTATION((0, 0), (50, 0), (25, 43))
SET MUTATIONPOINT triangle, 2, (25, 30)
PRINT mutationcount(triangle), "points"
PRINT "the top is at", mutationpointx(triangle, 2), mutationpointy(triangle, 2)
3 points
the top is at 25.0 30.0

Behaviors for every point

SET MUTATION BEHAVIOR gives a shape a behavior, and I move every point of it by that behavior, together, every tick:

SCREEN 320, 240
BEHAVIOR "drift"
    VELOCITY 2, 0
END BEHAVIOR

rock = MUTATION((20, 100), (40, 95), (50, 115), (30, 130), (15, 118))
SET POLYGON 0 !! bind(rock) color(7) fill(8)
SET MUTATION BEHAVIOR rock !! behavior("drift")

n = 0
WHILE n < 30
    IF COOLDOWN(1) THEN n = n + 1
WEND
PRINT "has it moved?", rock(0).x > 20
rock(0) = (60, 100)
n = 0
WHILE n < 30
    IF COOLDOWN(1) THEN n = n + 1
WEND
PRINT "half a second later, it's at", rock(0)
has it moved? 1
half a second later, it's at (60, 100)

Reading a point while the behavior moves it is fine, as often as you like. Writing one is you saying where the shape is now, so the behavior stops, and the rock stays where you put its point.

A shape doesn’t have to be bound to a polygon for a behavior to move it. Here, a searchlight: the lamp is a shape of one point that stays put, the far end of the beam is a shape of two points that a behavior sweeps back and forth, and the loop joins them with + and draws the triangle they make.

SCREEN 320, 240
LOADIMG "city.png", "city"
PALETTE 20, 255, 255, 160, 100

BEHAVIOR "sweep"
    PATH [(0, 0), (260, 0)] !! speed(2) pingpong
END BEHAVIOR

lamp = MUTATION((160, 10))
beam = MUTATION((60, 240), (0, 240))
SET MUTATION BEHAVIOR beam !! behavior("sweep")

DO
    IF COOLDOWN(1)
        MEMORYBUFFER "light"
            POLYGON lamp + beam, 20, 20
        END MEMORYBUFFER
        FRAME
            CLS
            PUTIMAGE (32, 0), "city"
            PUTIMAGE (0, 0), "light"
        END FRAME
    END IF
LOOP

PATH takes the beam from where it starts to 260 pixels to the right, and pingpong brings it back again. Color 20 is a pale yellow with an alpha of 100, so the city shows through the light.