The last thing the TI community needs is another bad game. I spent 6 months programming ASM before I ever made my first game (Z-Blitz, the only game I've had time to program). If you make a game at this early stage in your programming career, it will suck shlong; nonetheless, I am going to give you information on how to use the sprite routines used in game programming. These routines have been thoroughly optimised and should be used if they fit your circumstances (in general it's better to write your own routines), but even still, you should be able to understand how they work before you program -- I'm of the opinion that if you are not capable of writing a working sprite routine yourself, you are not ready to begin using other people's routines. Also, alot more goes into making a game than sprites; if you want to make a game right, you will need at least some ASM experience.
The TI-86 LCD is 128 by 64 pixels. If you calculate
the total number of pixels, considering that 8 pixels can be stored into
a byte, you will conclude that 1024 is the minimum number of bytes that
can be told to represent the entire LCD. In fact, this information
is represented in 1024 bytes, in an area of RAM called the video memory
($fc00 to $ffff). As the video memory address increases, the corresponding
group of pixels moves from left to right, then top to bottom once a new
row is reached, beginning at the top left of the screen. I say "group
of pixels" because each byte of video memory consists of eight pixels,
the most significant byte representing the left-most pixel. 16 bytes
fit exactly into one horizontal row of pixels before the video memory moves
down to the next row.
To change video memory, and therefore the screen,
you simple write to this area of RAM. <ld a,%1111111 / ld ($fc00),a>
will turn on the first eight pixels of the LCD, if they are not on already.
To to turn off a pixel, turn off its parallel byte of video mem.
Here's an example of how to clear every LCD pixel:
ld bc,1024
;1024 is the length of the video mem
ld hl,$fc00
;point HL to first byte of video mem
sub a
;clear the accumulator
ld (hl),a
;clear first byte (first 8 pixels) of vid mem
ld d,h
;copy HL into DE
ld e,l
;DE points to video memory as well
inc e
;DE points to $fc01
ldir
;as each cleared byte is copied to DE, HL sends it back to be
;copied again in the next byte (think about how ldir works)
FindPixel is the most basic of all graphics algorithms, and forms a basis for the more complicated algorithms. FindPixel inputs an (x,y) coordinate and returns the byte of video memory where that coordinate is located in video memory. The following FindPixel routine was written by CLEM:
;Input: b,c = x,y coordinates; 0-127,0-63; 0,0 = top left
;Output: (HL) = byte in video memory, A = bitmask (ie, the precise
bit in (HL))
FindPixel:
ld h,63
;HL will point to $fc00 from this after being shifted twice left
ld a,c
;get y coordinate in A
add a,a
;multiply it by 4
add a,a
ld l,a
;and put it into L
add hl,hl
;H will be shifted into $fc00, L (which was y*4 is mult by 4 again)
add hl,hl
;HL is now effectively $fc00+y*16 (the vertical offset in video mem)
ld a,b
;get x coordinate in B
rra
;divide it by 8 to get horiz byte from pixel (8 pixels in a byte)
rra
rra
add a,l
;add the vert offset to horz offset in video mem
ld l,a
;put result into L; HL has now been determined
ld a,b
;get x coordinate back into A (we'll now find bitmask)
and 7
;the bottom 3 bits represent the offset in the byte
ld bc,FP_Bits ; so we want
to make that a bitmask using the FP_Bits table
add a,c
;add bit offset of pixel (0-7) to pointer in BC
ld c,a
;to make BC point to bitmask (offset from FP_Bits
adc a,b
;if it happens to carry, ajdust offset by adding the carry
sub c
;and then subtract C, which we added inadvertantly
ld b,a
;put the carry (or no carry) into B
ld a,(bc)
;and get the bitmask
ret
FP_Bits: .db %10000000,%1000000,%100000,%10000,%1000,%100,%10,%1
;these 8 bytes represent 1 of 8 possible bitmasks for (x,y)
;we find the bitmask by adding A to BC, which points to this
To get the column from the x coordinate, FindPixel divides x by 8 (since there are 8 pixels in a byte, this will produce a number between 0 and 15 -- the number of bytes in a row). Similarly, to get the video memory byte from the row, FindPixel multiplies the y coordinate by 16; FindPixel adds the two together and adds $fc00 (this routine adds $fc00 through a clever shifting process). The FindPixel bitmask (a single bit set in a byte, representing the pixel to set within that byte) is found by adding the three least significant bits of x (0-7; the x coordinate lies within that byte of video memory) to the FP_Bits pointer. Here is an example of how to use FindPixel:
To set the pixel:
ld bc,$3317
;b=$33, c=$17
call FindPixel
;get video memory offset w/ bitmask
or (hl)
;OR bitmask with (HL)
ld (hl),a
;put result to video memory
To reset the pixel:
ld bc,$3317
call FindPixel
cpl
;complement each bit of the accumulator (see below)
and (hl)
;AND inverted bitmask with (HL)
ld (hl),a
To XOR the pixel:
ld bc,$3317
call FindPixel
xor (hl)
ld (hl),a
To test the pixel:
ld bc,$3317
call FindPixel
and (hl)
;will set zero flag if pixel is clear, reset zero flag if pixel is set
This should help give you a more inherent understanding
of the logical operators.
One instruction that you haven't seen yet is <cpl>,
which complements (inverts) each bit of the accumulator, produciong the
same result as would <xor $ff>:
%10111001
cpl:
%01000110
If A is a FindPixel bitmask with, say, bit 2 set:
%00000100
cpl:
%11111011
Following this with <and (hl)>, will reset the bit in question, meanwhile preserving all other bits.
All sprite routines use FindPixel as their base. A sprite is a graphic (usually eight pixels by eight pixels) that can be put to the screen at any given coordinate (a good example of a sprite is the Sqrxz bug). The most basic sprite routine is an aligned sprite routine, meaning that each sprite will always lie on horizontal intervals of eight pixels (corresponding to one byte of memory), so there's no need for a FindPixel bitmask.
;Input: B,C=x,y; DE = pointer to sprite
;Output: aligned sprite to video memory
AlignedSprite:
;first we will use the beginning half of FindPixel from above to get HL
ld h,63
;HL will point to $fc00 from this after being shifted twice left
ld a,c
;get y coordinate in A
add a,a
;multiply it by 4
add a,a
ld l,a
;and put it into L
add hl,hl
;H will be shifted into $fc00, L (which was y*4 is mult by 4 again)
add hl,hl
;HL is now effectively $fc00+y*16 (the vertical offset in video mem)
ld a,b
;get x coordinate in B
rra
;divide it by 8 to get horiz byte from pixel (8 pixels in a byte)
rra
rra
add a,l
;add the vert offset to horz offset in video mem
ld l,a
;put result into L; HL has now been determined
ex de,hl
;HL=pointer to sprite, DE = video memory location
ld bc,8
;counter; for an 8x8 sprite there are 8 bytes
PutLoop:
ld a,15
;we will use A to inc DE by 15 each loop (16 total counting ldi)
ldi
;copy byte of sprite to video memory; inc HL, inc DE, dec BC
ret po
;return when BC=0
add a,e
;point DE to the same column, next row of video memory
ld e,a
;can't keep it in A, though
jr nc,PutLoop ;continue if doesn't
carry
inc d
;but if it does, adjust D
jr PutLoop
;and then continue
Here is an example of it in action:
ld bc,$2810
;note: anything between bytes will not be calculated
ld de,sprite
;load DE with a pointer to the sprite
call AlignedSprite ;put it to the LCD
;more code
sprite:
;a simple square box (notice how 8x8 pixels fit into 8 bytes)
.db %11111111
.db %10000001
.db %10000001
.db %10000001
.db %10000001
.db %10000001
.db %10000001
.db %11111111
Something needs to be said about the carry in PutLoop.
The video memory can be divided into four parts, with essentially each
1/4 the screen being one of those parts (from top to bottom; the first
part is $fc00 to $fcff, the second is $fd00 to $fdff, etc.) From
these four parts are three boundaries ($fd00, $fe00, $ff00), which are
the three places in video memory where a carry can occur. The carry
would constitute an increase when pointing to the next sprite row that
lapses one of these boundaries -- any time E increases from less
than or equal to $ff to greater than or equal to $00 the carry flag will
set and the D must be incremented once. Since A can't be added to
DE directly, we must add A to E, then increment D if there was a carry.
Some aligned sprite routines put sprites only to every eighth vertical
pixel in addition to every eighth horizontal pixel; these routines will
never carry because the sprite will never cross one of the three "carry
boundaries".
I hope you understood video memory well enough to follow the
last routine. If you didn't, go back, because it's only going to
get worse...
The second kind of sprite routine, a PutSprite routine, is able to put a sprite anywhere to video memory. The idea is that you use either the FindPixel bitmask or bottom three bits of the x coordinate to shift each byte of the sprite into the next byte. Here's a PutSprite routine that I wrote (as far as I know, it's both faster and smaller than any other routine):
;Input: b,c = x,y; ix = 8x8 sprite location
;Output: sprite drawn; ix points to byte after sprite; bc unchanged
PutSprite:
push bc
;save bc so that this routine can boast preserving it
ld h,63
;CLEM's Findpixel
ld a,c
; |
add a,a
; |
add a,a
; |
ld l,a
; |
add hl,hl
; |
add hl,hl
; |
ld a,b
; |
rra
; |
rra
; |
rra
; |
add l
; |
ld l,a
; |
ld a,7
; |
and b
; \ /
ld d,a
;copy the bit offset into D for a shift counter
ld e,8
;use E for row counter
ps_loop:
;main loop, executed 8 times (once for each row, or byte of the sprite)
ld b,d
;B is destroyed each loop, so use D to preserve it
ld a,(ix)
;get current byte of sprite
inc ix
;and then point to next byte
ld c,0
;clear C so that we can shift B into it
call bit_shift
;AC is shifted right the number of times in B
xor (hl)
;put A (first half of sprite) to video memory using XOR
ld (hl),a
;you could change XOR to OR, but do it below too
inc l
;point to next byte of video memory
ld a,(hl)
xor c
;put C (second half of sprite) to video mem using XOR
ld (hl),a
ld a,15
call add_hl_a ;add 15 to HL (L
was incremented once before for a total of +16)
dec e
;decrement row counter
jr nz,ps_loop ;loop until zero
pop bc
;restore BC
ret
;end it
add_hl_a:
add a,l
ld l,a
ret nc
inc h
ret
bit_shift:
dec b
;loop as many times as the bit offset in the byte of vid mem
ret m
;ret once B becomes negative
srl a
;shift A right
rr c
;shift any carry into C (C starts as zero)
jr bit_shift
To get the sprite "between" bytes, you must shift each byte of the sprite the required number of bits to the right, and then put two bytes to video memory -- one at and one after the byte found in FindPixel. It's like putting two sprites right next to each other. Here is an example of how to use PutSprite; try compiling this to help you understand both the video memory and sprite routines.
ld bc,$1317 ;put sprite to this
location of video memory, indexed by the top left pixel
ld ix,spaceship ;load ix with a pointer to the space ship
sprite
call PutSprite ;put it to video memory
;more code
spaceship:
%00000000
%01000010
%11100111
%01000010
%11011011
%11111111
%01111110
%11011011
To move a sprite, check for keypresses and increment / decrement B or C. I'll give an example when we get into more complex sprite routines; right now, I'm going to take a short break to talk about interrupts...