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 POLYGONtakessolid,tagandvelocitytoo, 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.