← Back to Courses
module
20

Bigger sprites

Why this matters

An 8x8 sprite is a bullet, a bonus, a face. It is not a hero. On a screen 256 pixels wide, eight pixels is not much to build a game around - and stitching several small sprites together by keeping their coordinates in step is fiddly, eats attribute entries, and walks straight into the four-per-line limit.

The VDP has a better offer. One register bit makes every sprite 16x16 - four times the picture, still one attribute entry, still positioned to the pixel, still moved with two bytes. A second bit doubles the drawing again.

One bit, four patterns

Register 1's working value on this machine is 224. Add 2 - setting bit 1 - and sprites become 16x16:

        LD A,226        ; 224 + 2: sprites are now 16x16
        OUT (9),A
        LD A,129        ; 128 + register 1
        OUT (9),A

This is a mode, not a per-sprite setting: every sprite on the screen changes size together. A game picks one sprite size and builds for it.

A 16x16 sprite needs four times the pattern data, and the VDP gets it by grouping four consecutive patterns into one shape. The order is the part to learn, because it is not reading order. The four 8x8 patterns fill the sprite down the left column first, then down the right:

        +-------+-------+
        |   n   |  n+2  |
        +-------+-------+
        |  n+1  |  n+3  |
        +-------+-------+

So a shape is stored as: top-left, bottom-left, top-right, bottom-right - two tall halves, left half first. Store it in reading order instead and the sprite comes out with its quarters swapped.

Groups of four

In 16x16 mode the pattern number in the attribute table stops meaning "one pattern" and starts meaning "a group of four" - and the VDP ignores its bottom two bits. Pattern numbers 0, 1, 2 and 3 all display the same group, the one built from patterns 0-3. The next group is 4, then 8, then 12.

Two consequences:

  • The pattern table holds 256 patterns, so there are 64 possible 16x16 shapes, at group numbers 0, 4, 8, ... 252. Number your shapes in fours.
  • The table is still the font. An 8x8 sprite pattern trampled one character; a 16x16 group tramples four. The blank control range, codes 0 to 31, now gives you eight free groups - 0, 4, 8, 12, 16, 20, 24, 28 - before you start spending letters.

The code

A ship, sixteen pixels of it, from one attribute entry:

        ORG 256

        ; --- the four quadrants of the ship, into sprite patterns 0-3
        LD A,0
        OUT (9),A
        LD A,88
        OUT (9),A

        LD HL,ship
        LD B,32
sp:     LD A,(HL)
        OUT (8),A
        INC HL
        DJNZ sp

        ; --- register 1: the machine's value plus the size bit
        LD A,226
        OUT (9),A
        LD A,129
        OUT (9),A

        ; --- one sprite, one attribute entry
        LD A,0
        OUT (9),A
        LD A,123
        OUT (9),A

        LD A,120        ; Y - the top edge lands on line 121
        OUT (8),A
        LD A,120        ; X
        OUT (8),A
        LD A,0          ; group 0 - patterns 0 to 3
        OUT (8),A
        LD A,15         ; white
        OUT (8),A

        JP $

ship:   ; top-left quadrant
        DEFB 00000001b
        DEFB 00000011b
        DEFB 00000011b
        DEFB 00000111b
        DEFB 00000110b
        DEFB 00001110b
        DEFB 00001111b
        DEFB 00011111b
        ; bottom-left quadrant
        DEFB 00011111b
        DEFB 00111111b
        DEFB 00111001b
        DEFB 01111001b
        DEFB 01100111b
        DEFB 11001111b
        DEFB 11011000b
        DEFB 10010000b
        ; top-right quadrant
        DEFB 10000000b
        DEFB 11000000b
        DEFB 11000000b
        DEFB 11100000b
        DEFB 01100000b
        DEFB 01110000b
        DEFB 11110000b
        DEFB 11111000b
        ; bottom-right quadrant
        DEFB 11111000b
        DEFB 11111100b
        DEFB 10011100b
        DEFB 10011110b
        DEFB 11100110b
        DEFB 11110011b
        DEFB 00011011b
        DEFB 00001001b

Run it over whatever screen you like - S17's, or the plain machine.

What you should see

A white ship, sixteen pixels square, its top-left corner at (120, 121) - Y's off-by-one from S19, unchanged. Nose, body, fins and exhaust: a game character, not an icon. One attribute entry put it there, and moving it is still the same two bytes it was in S19.

A white 16x16 ship sprite built from four pattern quadrants

Read the data against the picture before moving on: the first sixteen bytes are the ship's whole left half, top to bottom, and the second sixteen are its right. That is the order the hardware wants, and the reason the listing labels the quadrants.

The other bit: magnification

Bit 0 of register 1 doubles the drawing of every sprite - each pattern pixel becomes a 2x2 block on screen:

        LD A,225        ; 224 + 1: every sprite drawn double size
        OUT (9),A
        LD A,129
        OUT (9),A

An 8x8 pattern now covers 16x16 pixels of screen. Nothing in VRAM changes - same 8 bytes, same attribute entry, same top-left positioning - the chip simply draws each pixel twice as wide and twice as tall, so the result is exactly the shape you designed, chunkier.

Set both bits - 227 - and a 16x16 group is drawn at 32x32: an eighth of the screen's width, from 32 bytes of pattern and one attribute entry. Magnification is the cheap half of that deal, and it looks it: designs keep their layout but every edge doubles. For a boss or a title screen it is free size; for a hero, most games prefer the real pixels of plain 16x16.

When the shape is not square

The size bit only makes squares. A rocket is tall, a train is wide, and for those there is a technique older than the register: build the shape from more than one sprite. Each sprite is independent, positioned to the pixel, so two sprites drawn edge to edge read as one object. A body at (120, 120) covers columns 120-127; put its other half at (128, 120) and the pixels are adjacent - there is no seam because there is nothing between them.

Stacked vertically it makes a tall shape, and it buys two things the size bit cannot sell:

  • A colour per part. A 16x16 group has one attribute entry and therefore one colour byte for all four quadrants. Two stacked sprites have two.
  • Parts that switch on and off. Each part has its own attribute entry, so a rocket's flame can vanish while its body stays.

Here is exactly that - a white rocket with a yellow flame, from two 8x8 sprites (leave register 1 alone this time; these are ordinary small sprites):

        ORG 256

        ; --- two patterns: body, then flame
        LD A,0
        OUT (9),A
        LD A,88
        OUT (9),A
        LD HL,rocket
        LD B,16
pat:    LD A,(HL)
        OUT (8),A
        INC HL
        DJNZ pat

        ; --- two sprites, stacked
        LD A,0
        OUT (9),A
        LD A,123
        OUT (9),A
        LD HL,attrs
        LD B,9
lp:     LD A,(HL)
        OUT (8),A
        INC HL
        DJNZ lp
        JP $

attrs:  DEFB 120,120,0,15       ; body: white
        DEFB 128,120,1,11       ; flame: light yellow, directly below
        DEFB 208

rocket: ; body
        DEFB 00011000b
        DEFB 00111100b
        DEFB 00111100b
        DEFB 01111110b
        DEFB 01111110b
        DEFB 11111111b
        DEFB 11011011b
        DEFB 10011001b
        ; flame
        DEFB 01011010b
        DEFB 01111110b
        DEFB 01111110b
        DEFB 00111100b
        DEFB 00111100b
        DEFB 00011000b
        DEFB 00010000b
        DEFB 00001000b

What you should see: an 8x16 rocket - white hull with fins, yellow flame underneath, no gap at the join. The body's Y of 120 puts its rows at 121-128; the flame's Y of 128 continues at 129 exactly.

A white rocket body with a yellow flame sprite stacked directly beneath it

Two rules for composites:

Move every part together. The shape exists only in the coordinates. One routine should place the whole object from a single position - body at (x, y), flame at (x, y+8) - so the parts cannot drift.

Hide a part with colour 0, not with Y = 208. Transparent is a sprite that is still processed but invisible - write the flame's colour byte as 0 and the rocket coasts. Writing 208 into its Y switches off every sprite after it in the table, which is rather more than a flame.

And the cost, which is where the shape's direction matters: a vertical stack puts its parts on different scan lines, so the rocket spends no more of any line's four slots than a single sprite would. A horizontal pair spends two slots on every line it crosses - two side-by-side saucers and one bullet, and something on that line is already not drawn. Wide composites are the expensive kind.

When one colour is not enough

A sprite has one colour byte, so a sprite is one colour - but nothing says a character has to be one sprite. Put two sprites at the same position and the VDP layers them: S19's rule that the lower-numbered sprite draws in front is now a tool rather than a fact. The front layer's set pixels show in its colour; wherever it has zeros, the layer behind shows through.

Design the layers as complements - each pixel of the picture belonging to exactly one layer - and the pair reads as a single two-colour image:

        ORG 256

        ; --- two patterns: eyes, then body
        LD A,0
        OUT (9),A
        LD A,88
        OUT (9),A
        LD HL,alien
        LD B,16
pat:    LD A,(HL)
        OUT (8),A
        INC HL
        DJNZ pat

        ; --- two sprites, same position: lower number in front
        LD A,0
        OUT (9),A
        LD A,123
        OUT (9),A
        LD HL,attrs
        LD B,9
lp:     LD A,(HL)
        OUT (8),A
        INC HL
        DJNZ lp
        JP $

attrs:  DEFB 120,120,0,15       ; eyes: white, in front
        DEFB 120,120,1,3        ; body: light green, behind
        DEFB 208

alien:  ; eyes - only the pixels the body leaves open
        DEFB 00000000b
        DEFB 00000000b
        DEFB 00100100b
        DEFB 00000000b
        DEFB 00000000b
        DEFB 00000000b
        DEFB 00000000b
        DEFB 00000000b
        ; body - eye holes punched out
        DEFB 00111100b
        DEFB 01111110b
        DEFB 11011011b
        DEFB 11111111b
        DEFB 10111101b
        DEFB 10100101b
        DEFB 00100100b
        DEFB 01000010b

What you should see: a small green creature at (120, 121) with two white eyes. Look at the two patterns against each other: the body's third row is 11011011 and the eye row is 00100100 - each 1 in one is a 0 in the other. The eyes sit in holes the body leaves open.

A small green alien sprite with two white eyes layered from two sprites at the same position

This layering, the side-by-side pair and the stack are all the same idea - several attribute entries pretending to be one object, moved together - and they combine: professional games on this chip built their heroes from a 16x16 group plus an overlay layer for the second colour.

The bill is the line budget. Every layer is a sprite, and overlaid layers sit on the same scan lines by definition. A two-layer character spends two of a line's four slots; two two-layer characters side by side spend all four, and a bullet crossing them vanishes. Layers are the most expensive pixels in the machine - spend them on the hero, not the crowd.

What did not change

Everything S19 established still holds, at every size:

  • Four sprites per scan line. A 16-pixel-tall sprite occupies sixteen lines, so big sprites collide with the limit more often, not less. Four ships fit side by side; a fifth on the same lines is not drawn.
  • 32 attribute entries, lower numbers in front, Y = 208 stops the list, the early clock bit, colour 0 transparent - and Y still names the line above the top.
  • Moving any of it is still two bytes.

Change one thing

  • Rearrange the four quadrant blocks into reading order - top-left, top-right, bottom-left, bottom-right. What comes out, and why exactly that?
  • Change the attribute's pattern number from 0 to 1, then 2, then 3. What changes? Then 4.
  • Delete the 32 pattern bytes and set the pattern number to 64, keeping the size bit. Four somethings appear. What are they, and why that arrangement?
  • Change 226 to 227. How big is the ship now, and how many bytes of pattern data did the change cost?
  • Change 226 back to 224 with the ship data still in place. What is on screen now, and what are the other three quarters of it doing?
  • In the rocket program, change the flame's colour from 11 to 0. Then put it back and change the flame's Y from 128 to 208 instead. Both hide the flame and this program cannot tell them apart - so add a third sprite, another body somewhere else on screen, and try both again. Now which hide is safe?
  • Turn the rocket into a side-by-side pair instead. How many of a scan line's sprite slots does it occupy now?
  • In the alien, change the body's third row from 11011011b to 11111111b. What happens to the eyes? Now swap the two attribute entries and look again. What does that tell you about who wins an overlap?

Exercises

20.1 - Your own character. Design a 16x16 character of your own on squared paper, split it into the four quadrant blocks in the order the hardware wants, and put it on screen. Keep the drawing: converting it is most of the work of sprite-making, and you will do it many times.

20.2 - Two frames. Build a second version of your character at group 4 - arms up instead of down, flame long instead of short. Its pattern data is 32 more bytes; switching the sprite between the two costs one attribute byte. Flip between them in a loop with a delay, and you have animation.

20.3 - A tall ship. Rebuild S20's ship as an 8x24 composite: three 8x8 sprites stacked, the exhaust in its own colour. Write one routine that places all three from a single (x, y).

20.4 - Shade it. Give your 20.1 character a second layer: pick out its highlights - an eye, a visor, a lamp - into a new pattern, punch them out of the original, and overlay the two in different colours.

When it goes wrong

Symptom Cause
No sprite at all, attributes correct The pattern group was never written. Groups step by four, and an unwritten group up in the control-code range is blank - there is nothing to see, rather than something wrong.
The sprite's quarters are swapped or scrambled The quadrants were stored in reading order. Left column first: top-left, bottom-left, top-right, bottom-right.
Every sprite changed size, not just one As designed. Bits 0 and 1 of register 1 are modes for the whole machine, not per-sprite settings.
The sprite is the right shape but blocky The magnify bit is set. 227 where you meant 226.
Sprites vanish when big ones line up Still four per line, and 16x16 sprites are on sixteen lines at once.
Hiding one part of a composite hid several sprites Its Y was parked at 208, which stops the whole list there. Hide a part with colour 0.

Summary

  • Register 1 controls sprite rendering: the machine's value is 224, +2 for 16x16 sprites, +1 to draw everything double. Both are whole-machine modes.
  • A 16x16 sprite is four consecutive patterns, left column first: top-left, bottom-left, top-right, bottom-right.
  • Pattern numbers select groups of four - the low two bits are ignored, so shapes live at 0, 4, 8, ... 252, sixty-four of them, and each group overlays four characters of the font. Codes 0-31 hold eight safe groups.
  • Magnification doubles the drawing, not the data: 8x8 becomes 16x16 on screen, 16x16 becomes 32x32, from the same bytes.
  • Four per line, 32 entries, Y's off-by-one, the 208 stop - all unchanged.
  • Non-square shapes are built from independent sprites moved together: a colour per part, parts that hide with colour 0 - and horizontal pairs cost two of a line's four slots, where vertical stacks cost one.
  • Two sprites at the same position layer by priority into one multi-colour image - design the layers as complements, and remember every layer spends a line slot.

Next

S21. Taking the controls. A rocket that sits still is scenery. The game loop - read the keys, move the variables, redraw the sprites - is the skeleton every game hangs off, and it comes with one structural rule that decides whether your program survives its own success.

Get the Newsletter

New guides, disk images and community finds, roughly once a quarter. No spam, we promise, this isn't Tatung's marketing department.
Your subscription could not be saved. Please try again.
Your subscription has been successful.

Newsletter

Subscribe to our newsletter and stay updated.