Sprites

z80 » Graphics

One of the most basic components of any image based game are sprites, small images which move about the screen at high speeds. The mouse cursor is a sprite that Windows© redraws thousands of times a second on the screen. The TI86 doesn't have color, just black and white. A bit set in the Video Memory, as you know, represents a pixel turned on (black). A sprite is made up of many bits in a row. Each bit in the sprite data represents a pixel on the screen.

You can play around with making your own sprites right now using SpriteEdit. Just copy and paste the results right into your source!

Now that you know how to put single pixels on the screen, you can move onto putting whole pictures onto the screen. We won't use FindPixel this time because that would take too long for each and every pixel in a 64 pixel image (8x8 pixel image). Instead we will be manipulating whole bytes at a time.

When you play Super Mario on your old 2D Nintendo 8-Bit, there are several characters that are moving on the screen at once. (I'm using the old Nintendo as an example because it uses 2 dimensional graphics like the TI86 does.) Those fast moving images are called sprites because they are small and are used constantly. Stuff like trees are called tiles because they make up the background total image along with, say, part of a castle wall.

You can't always just copy a sprite onto the screen and be done. Sometimes the sprite might overlap from one byte to the next one the screen. Take for example this to be a part of the video memory. Each 0 is a bit and each group of 8 0's is a byte. These bytes extend in all directions on the screen. The bits are zeros because it is clear, blank, no pixels set.

00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000

Here is the assembler code for the sprite we want to work with. .db tells the assembler the following is to be directly inputted into the program as bytes.

.db %00011000
.db %11011011
.db %01011010
.db %11011011
.db %01011010
.db %11011011
.db %00011000
.db %11100111

Here is a graphical representation of what the above will look like on the screen.

 
 
 
 
 
 
 
 

We are now going to copy the that sprite into the video memory where it will overlap two bytes. that it will overlap bytes

xxxxxx00 011000xx xxxxxxxx
xxxxxx11 011011xx xxxxxxxx
xxxxxx01 011010xx xxxxxxxx
xxxxxx11 011011xx xxxxxxxx
xxxxxx01 011010xx xxxxxxxx
xxxxxx11 011011xx xxxxxxxx
xxxxxx00 011000xx xxxxxxxx
xxxxxx11 100111xx xxxxxxxx

We need to now cover the routine that handles the writing of the image onto the Video Memory. This routine needs to figure out if the image runs over two bytes of is in one byte, how much of an overlap there is if it spans over two bytes, and then write the image taking the overlap into consideration.

I emailed Jay Hellrung when he just released his Sprite Display Routines asking for some info on them. From his reply I have put together the following on the standard SDR8.h routine included in the zip.

On the Download page you can find a zip with several Sprite Routines packaged for you to choose from.

The routine is pasted here and referenced with the following table by the line numbers. Follow each line and try to picture in your head what it is doing. The easiest way to do this is to print out the SDR8 Code and then refer to the explanations.

  1. SDR_8x8:
  2. ld a,63
  3. sub c
  4. ld c,a
  5. push hl
  6. call FindPixel
  7. ld c,8
  8. add a,a
  9. dec a
  10. ld e,a
  11. ld a,b
  12. and %00000111
  13. inc a
  14. ld b,a
  15. SDR_8x8_NewRow:
  16. ex (sp),hl
  17. ld a,(hl)
  18. inc hl
  19. ex (sp),hl
  20. push bc
  21. rlca
  22. SDR_8x8_PrepByte:
  23. rrca
  24. djnz SDR_8x8_PrepByte
  25. ld d,a
  26. and e
  27. or (hl)
  28. ld (hl),a
  29. inc hl
  30. ld a,e
  31. cpl
  32. and d
  33. or (hl)
  34. ld (hl),a
  35. ld c,$10-1
  36. add hl,bc
  37. pop bc
  38. dec c
  39. jr nz,SDR_8x8_NewRow
  40. pop hl
  41. ret

Line(s) Explanation Bytes
1 You need to use it like this:
	ld hl,sprite	;location of sprite
	ld bc,$1414	;coordinates
	call SDR_8x8	;call sprite routine
	ret		;done
PutSprite:		;the routine's label
			;paste jay's or some other sprite
			; routine here
			;make sure to paste findpixel if
			; it's needed
Sprite_Label:		;the sprite label
	.db %01111110	;first line
	.db %10011001	;second line
		. . .
		. . .
	.db %01111110	;eight line
		
none
2-4 We inputted the routine thinking that the origin (0,0) was at the top left. FindPixel thinks it's at the bottom left. We need to subtract 63 so we have the value from 64 (the height of the screen in pixels). 4
5 We are going to have to use (and therefore destroy) hl (pointing at the sprite) and then come back to it so we need to save it. 1
6 FindPixel just takes the coordinates and converts them into the exact address in the memory that needs to be edited for the desired pixel placement. It returns hl pointing to where in the video memory we want to mess with and a is (hl). 3
7 Most sprites are 8 bytes high and 1 byte (8 pixels) wide (8 x 8). This routine takes for granted that it is dealing with an 8x8 sprite. If you don't want your sprite to be 8 bytes high, then you need to change the 8 to whatever the height you want, but bear in mind that it means all your sprites are going to need to be the same height. 2
8-10 We may or may not be using all of a byte to put our sprite in. We add a to a and decrease it so that every bit is set from the far left being written to, all the way to bit 0. This used with and makes a bit mask so that we only manipulate the bits that are set in that byte. We need to save this bit mask into e for use with each row. 3
11-14 Now we need to set up for a djnz loop. We need to have the routine figure out how many times it needs to copy a bit from the sprite to the video memory. We do this using the 'scraps' from the find pixel routine. The find pixel routine divides the x coordinate by 8 to figure out its stuff. It leaves the remainder in b. We can use that to figure out how many times we need to the drawing loop. We mask out anything above 7 (line 12). Since it could be zero, we increase it so it's at least one (line 13); therefore, we have a number of the remainder somewhere between 1 and 8. We put that back into b (line 14) which tells us how many rows we have. 5
15 We are going to begin the loop that does each row. none
16-19 Remember that we pushed the location of the sprite to the stack. The sp register is the Stack Pointer which I will probably discuss more later. We switch what's at the sp with hl temporarily, so we are loading the first row of the sprite into a and then we increment hl so that it's ready for next time. After we're done, we put hl back where it was at the sp. 4
20 We want to save our rotating counter (b) and our row counter (c) for next time around. 1
21 We know we are going to rotate (will be discussed later) atleast once down below, so we rotate it once to the left so it'll be the same when we're through. 1
22-24 Since we already figured out the overlapping on the bytes, we just rotate the sprite part we have in a as many times as needed to fit into the bit mask we already figured out. We use our rotating counter already saved and in b and rotate the sprite part using a djnz loop. 3
25 We want to save what we did into d and we'll come back to it later in line 32. 1
26 Remember back to lines 8,9, and 10 where we got our bit mask. We now put it to use anding it with our part of our sprite we've rotated. 1
27-29 Now here is where we actually put the pixel on the screen using or with what's currently on the screen and what we've done and then putting the answer back on the screen! Then we just increase hl to point to where we're write to next in the next byte over! 3
30-31 Get our mask back into a and invert it so we have the mask for this new byte. 2
32 We and it again with our rotated sprite to get the other half rotated around that we didn't use yet. 1
33-34 We're going to do some more drawing. Like in lines 27-29, we're going to use or again and load the answer onto the screen! 2
35-36 We want to go to the next row since we're all done. We would normally add $10 to go to the byte directly under the one we are at now, but we want to go $10-1 because we want to be down and one byte to the left (or minus a byte). B was zero because it was used in a djnz loop ended when b was zero so we just have to use c which will determine the value of bc. 3
37 Time to restore our rotating counter (b) and our row counter (c) for use next time around. 1
38-39 We are using b as a rotation counter for use in the inner djnz loop and we are going to use c for an 'artificial djnz loop'. 3
40 We pushed the sprite location so we could use ex (sp),hl instead of another register pair. That saves time and space. We need to pop it off so we'll return to the right spot! 1
41 I'm not going to bother telling you. 1


More from z80 » Graphics
Find a Pixel // Grayscale // Pixel Manipulation // The Screen // Sprites // SDR8 Routine // Tile Maps // TileGen Routine