Some checks failed
akbasic CI Build / cmake_build (push) Failing after 3m20s
akbasic CI Build / sanitizers (push) Failing after 4m31s
akbasic CI Build / coverage (push) Failing after 3m40s
akbasic CI Build / akgl_build (push) Failing after 22s
akbasic CI Build / mutation_test (push) Failing after 3m28s
`SPRHIT n, kind [,x1, y1, x2, y2]` gives a sprite a collision shape: a box, a circle inscribed in it, or a capsule. `RSPHIT(n, f)` reads it back in SPRHIT's own argument order, the way RSPRITE and RSPPOS already do, and needs no device because it answers from interpreter state. The rectangle is two corners measured from the sprite's top-left, in device pixels -- the same `x1, y1, x2, y2` that `BOX` and `SSHAPE` take. A dialect with two spellings for a rectangle is one nobody can write from memory. Omit it and the shape fits whatever the picture turned out to be, which is what a sprite loaded from a file needs: `SPRSAV "ship.png", 1` takes the image's own size and the program never learns what that was. **A sprite nobody has shaped collides with its whole frame, expansion bits included, exactly as before.** That is a promise rather than a convenience, and it has its own test: the same two sprites in the same two places, once with no SPRHIT and once with a four-pixel box, reporting a collision and then not. Named SPRHIT rather than SPRSHAPE because "shape" already means "a region SSHAPE saved" in this dialect, in this very chapter -- `SPRSAV A$, 1` takes one -- and a reader who typed `SPRSHAPE A$, 1` would have had every reason to. Both names, and RSPHIT, were grepped against every label in docs/, examples/ and both corpora first: a bare word is a label here, so a verb and a label share one namespace and taking a name a checked-in listing already uses would break it silently. `SPRHIT n, 0` takes a sprite out of collision while leaving it on the screen -- the ghost, the flashing invulnerable player, the pickup already taken. Hiding it with `SPRITE n, 0` stops it colliding too, and is what you want when it should not be seen either. The circle answers the complaint chapter 8 already ships a figure of. That figure shows two discs whose *boxes* touch at a corner while the artwork is nowhere near, and `BUMP(1)` reporting a collision; two `SPRHIT n, 2` and it stops. The test asserts both halves so the figure's caption stays true. `tests/verbs_table.c` caught RSPHIT filed after RSPPOS rather than before it, which is the sorted-table test doing exactly the job it exists for. Docs: a new section in chapter 8, rows in the verb and function references in alphabetical order, and chapter 13's "collision is by bounding box" becomes "collision is by shape" with the addition named. 111 with akgl, 110 without, and the artwork breakout still runs clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwxGB6TdoVvZ11KQQME9cL
225 lines
7.4 KiB
Markdown
225 lines
7.4 KiB
Markdown
# 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
|
|
|
|
```basic requires=akgl setup=ship
|
|
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
|
|
|
|
```basic requires=akgl
|
|
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
|
|
|
|
```basic norun
|
|
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
|
|
|
|
```basic requires=akgl screenshot=sprites
|
|
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
|
|
|
|
```basic norun
|
|
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.
|
|
|
|
```basic requires=akgl screenshot=sprite-collision
|
|
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.
|
|
|
|
## Giving a sprite a shape
|
|
|
|
That false positive is what `SPRHIT` is for. It says what part of a sprite collides,
|
|
rather than leaving it as the whole picture:
|
|
|
|
```basic norun
|
|
SPRHIT n, kind
|
|
SPRHIT n, kind, x1, y1, x2, y2
|
|
```
|
|
|
|
| `kind` | Is |
|
|
|---|---|
|
|
| 0 | nothing — the sprite stays on screen and stops colliding |
|
|
| 1 | a box |
|
|
| 2 | a circle, inscribed in the rectangle |
|
|
| 3 | a capsule, round ends left and right |
|
|
| 4 | a capsule, round ends top and bottom |
|
|
|
|
The rectangle is **two corners measured from the sprite's top-left**, in device pixels —
|
|
the same `x1, y1, x2, y2` that `BOX` and `SSHAPE` take, because a dialect with two
|
|
spellings for a rectangle is one nobody can write from memory. Leave it out and the shape
|
|
fits whatever the picture turned out to be, which is what a sprite loaded from a file
|
|
needs: `SPRSAV "ship.png", 1` takes the image's own size and the program never learns what
|
|
that was.
|
|
|
|
So the two discs above stop colliding as soon as they are discs:
|
|
|
|
```basic norun
|
|
90 SPRHIT 1, 2
|
|
100 SPRHIT 2, 2
|
|
```
|
|
|
|
and a ship whose art does not fill its frame can say so:
|
|
|
|
```basic norun
|
|
SPRHIT 1, 1, 4, 2, 20, 19
|
|
```
|
|
|
|
**A sprite nobody has shaped collides with its whole frame**, expansion bits included,
|
|
which is what every sprite did before `SPRHIT` existed. Adding the verb changed no existing
|
|
program.
|
|
|
|
`SPRHIT n, 0` is the ghost, the flashing invulnerable player and the pickup that has
|
|
already been taken: still drawn, no longer in the way. Hiding the sprite with `SPRITE n, 0`
|
|
also stops it colliding, and is what you want when it should not be seen either.
|
|
|
|
`RSPHIT(n, f)` reads it back, in `SPRHIT`'s own argument order — 0 the kind, then 1 to 4
|
|
for the two corners. It needs no sprite device, the way `RSPPOS` does not.
|
|
|
|
## 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.
|