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.
- ↓SDR_8x8:
- ↓ ld a,63
- ↓ sub c
- ↓ ld c,a
- ↓ push hl
- ↓ call FindPixel
- ↓ ld c,8
- ↓ add a,a
- ↓ dec a
- ↓ ld e,a
- ↓ ld a,b
- ↓ and %00000111
- ↓ inc a
- ↓ ld b,a
- ↓SDR_8x8_NewRow:
- ↓ ex (sp),hl
- ↓ ld a,(hl)
- ↓ inc hl
- ↓ ex (sp),hl
- ↓ push bc
- ↓ rlca
- ↓SDR_8x8_PrepByte:
- ↓ rrca
- ↓ djnz SDR_8x8_PrepByte
- ↓ ld d,a
- ↓ and e
- ↓ or (hl)
- ↓ ld (hl),a
- ↓ inc hl
- ↓ ld a,e
- ↓ cpl
- ↓ and d
- ↓ or (hl)
- ↓ ld (hl),a
- ↓ ld c,$10-1
- ↓ add hl,bc
- ↓ pop bc
- ↓ dec c
- ↓ jr nz,SDR_8x8_NewRow
- ↓ pop hl
- ↓ ret
↑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 and ing 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
| |