Files
akbasic/docs/08-sprites.md
Andrew Kesterson 16b38c1138 Generate the documentation's figures from the listings they illustrate
Chapters 6 and 8 described what a verb draws in prose. Eight figures now show
it, and each one is produced by running the BASIC listing printed immediately
above it -- so a picture cannot drift away from the code beside it, which is the
way a screenshot goes wrong and the way nothing notices.

tools/screenshot.c is a second SDL host, much smaller than the frontend: dummy
video driver, software renderer, run to completion, read the target back, write
a PNG. It draws no text layer on purpose, so a READY in the corner is not noise
in a figure about BOX and no font has to be resolved.

tools/docs_screenshots.sh reads the new screenshot=NAME fence tag straight out
of the markdown. size=WxH is the second tag, and SCALE's figure uses it: the
point being made is a 320x200 listing filling a larger window, which cannot be
made on a 320x200 surface.

Two gates, answering different questions. docs_examples fails a tagged block
with no image, in both configurations, so a figure cannot be added and
forgotten. docs_screenshots -- a CTest, AKGL build only -- re-renders every
figure and compares byte for byte, so a listing edited without regenerating
fails. Only the second catches a stale picture.

The PNGs are checked in because a reader on the forge has no build tree, and
docs/images/README.md says loudly that they are generated. Regenerating is never
part of a build: the target is run deliberately, so a make cannot put eight
binary diffs in front of whoever ran it.

Drawing the BOX figure caught a defect in TODO.md itself. Deviation 16 claimed
in bold that BOX fills on a negative angle while its own paragraph said the fill
was filed rather than implemented. BOX cannot fill, and filled_rect is reached
by no verb as a result.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 07:37:04 -04:00

5.7 KiB

8. Sprites

Eight sprites, numbered 1 to 8, as on a C128. They need the SDL build.

A sprite here is a real object in the host's graphics library, registered alongside whatever the host's own game is drawing — so a game that embeds this interpreter can see and manipulate a script's sprites.

Giving a sprite a picture

SPRSAV does it, and it takes three kinds of source.

From an image file

10 SPRSAV "ship.png", 1

The most useful form, and not one a C128 has. The path is tried against the working directory first and then against the directory the running program was loaded from — so a .bas stored beside its artwork works wherever you launch it from. Anything the image library can decode will do.

A sprite loaded this way takes the image's own size. It is not forced to 24 by 21.

From a saved region

10 BOX 1, 0, 0, 24, 21
20 SSHAPE A$, 0, 0, 24, 21
30 SPRSAV A$, 1

Draw it with the graphics verbs, capture it, install it. The C128 documents SPRSAV's string as the SSHAPE data format at a fixed 24 by 21, so sharing the mechanism is faithful rather than a shortcut.

From data

10 DIM P#(63)
20 FOR I# = 0 TO 62
30   READ P#(I#)
40 NEXT I#
50 SPRSAV P#, 1
60 DATA 255, 129, 129, ...

63 bytes: three per row, twenty-one rows, most significant bit leftmost. This is the form a type-in listing uses.

It takes an integer array, not a string. A C128 puts the raw bytes in a string; a string here is NUL-terminated, so it cannot hold a zero byte and therefore cannot hold a sprite. DIM P#(63) is exactly the right size.

Writing a sprite back out — SPRSAV 1, A$ — is refused. That would be a disk operation.

Showing and moving

10 COLOR 1, 2
20 CIRCLE 1, 20, 20, 18, 18
30 PAINT 1, 20, 20
40 SSHAPE A$, 0, 0, 40, 40
50 GRAPHIC 1, 1
60 FOR N# = 1 TO 3
70   SPRSAV A$, N#
80 NEXT N#
90 SPRITE 1, 1
100 SPRITE 2, 1, 6
110 SPRITE 3, 1, 8, 0, 1, 1
120 MOVSPR 1, 30, 80
130 MOVSPR 2, 110, 80
140 MOVSPR 3, 190, 60

Three sprites from one drawing: the first in the default colour, the second in colour 6, and the third in colour 8 with both expansion bits set, which is what makes it twice the size. GRAPHIC 1, 1 on line 50 wipes the drawing the sprites were captured from — the picture would otherwise still show the original disc in the top-left corner.

A sprite's colour multiplies the artwork rather than replacing it, so a white disc takes the colour cleanly and a coloured one comes out darker than you asked for.

SPRITE n [,on] [,colour] [,priority] [,xexpand] [,yexpand] [,multicolour]. Only the number is required and an argument you leave out is left alone, so SPRITE 1, 1 turns sprite 1 on without disturbing its colour.

MOVSPR has four forms, and the punctuation is what tells them apart:

Form What it does
MOVSPR 1, 100, 50 put it at (100, 50)
MOVSPR 1, +10, -20 move it by that much
MOVSPR 1, 10 ; 90 move it 10 pixels along bearing 90
MOVSPR 1, 45 # 8 set it moving along bearing 45 at speed 8

A bearing is degrees clockwise from straight up, so 0 is north and 90 is east. In the polar form the distance comes first and the angle second — the opposite order from the continuous form, which is a trap worth remembering.

The continuous form does not move anything on the statement itself. The sprite moves as the program runs, paced by the host's clock. Speed 0 stops it.

Coordinates are the same window pixels the drawing verbs use, not the VIC-II's raster coordinates — see Chapter 6, and RGR(1) and RGR(2) for how big the window is. SCALE does not apply to them: a sprite is positioned in device pixels whatever the drawing verbs are doing.

Collision

10 COLLISION 1, BUMPED
20 REM ... main loop ...
90 GOTO 20
100 LABEL BUMPED
110 PRINT "HIT: " + BUMP(1)
120 RETURN

COLLISION 1, target calls a subroutine when two sprites overlap. The handler is entered between lines and must end in RETURN, exactly like a GOSUB body. Omitting the target disarms it.

BUMP(1) returns a bitmask of which sprites have collided — bit 0 is sprite 1 — and reading it clears it, which is what makes "has anything hit me since I last looked" answerable.

Only type 1, sprite-to-sprite, is implemented. Types 2 and 3 are refused by name.

Collision is by bounding box, not by pixel: two sprites whose boxes overlap but whose artwork does not are reported as colliding.

10 COLOR 1, 2
20 CIRCLE 1, 20, 20, 14, 14
30 PAINT 1, 20, 20
40 BOX 1, 0, 0, 40, 40
50 SSHAPE A$, 0, 0, 40, 40
60 GRAPHIC 1, 1
70 SPRSAV A$, 1
80 SPRSAV A$, 2
90 SPRITE 1, 1, 6
100 SPRITE 2, 1, 3
110 MOVSPR 1, 110, 55
120 MOVSPR 2, 145, 90

The artwork here includes a border around the whole 40 by 40 sprite, so each sprite draws its own bounding box. Those boxes overlap at one corner and the two discs are nowhere near each other — and BUMP(1) reports a collision.

Reading state back

Function Gives
RSPPOS(n, 0) x
RSPPOS(n, 1) y
RSPPOS(n, 2) speed
RSPRITE(n, f) one of SPRITE's settings, in SPRITE's own argument order
RSPCOLOR(1) or RSPCOLOR(2) one of the shared multicolour registers

These read the interpreter's own state rather than asking the device, so they work even with no device attached.

SPRDEF

Not implemented, and deliberately. It is an interactive full-screen sprite editor driven by single keystrokes, not something a program can call — and this interpreter does not own the screen it would take over. The three SPRSAV forms replace it.