xxxxxxxx xxxxxxxx xxxxxxxx xxx00111 100xxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxx01111 110xxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxx11110 011xxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxx11100 011xxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxx11000 111xxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxx11001 111xxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxx01111 110xxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxx00111 100xxxxx xxxxxxxxCases like these can be remedied through shifting each row of the sprite until it's split appropriately, then bitmasking the split halves into the graph buffer. The amount that the sprite needs to be shifted by is found by dividing its x-compontent by 8 and taking the remainder. In this case, the sprite should be shifted by 27 % 8 = 3 times to the right.
If we use XOR logic to display the sprite, then the black parts will reverse the pixels they intersect with, and the white parts will be transparent. All right, but if two sprites intersect, or the background is highly detailed, the result will look like garbage.
What about OR? In that case the black parts will be black no matter what. This is a nice solution to the problem of intersecting sprites.
What if we have a mostly black background, and we want to display white sprites? Then we can invert the sprite image and use AND. Now the white parts of the sprite will force pixels off, and this time its the black pixels that will be transparent (turnabout is fair play).
PutSpriteXOR: ; A = x coordinate ; E = y coordinate ; B = number of rows ; IX = address of sprite LD H, 0 LD D, H LD E, L ADD HL, HL ADD HL, DE ADD HL, HL ADD HL, HL LD E, A SRL E SRL E SRL E ADD HL, DE LD DE, PlotSScreen ADD HL, DELocate the address the sprite image starts in. This is ripped directly off of GetPixel.
AND 7 JR Z, _AlignedModulo A with 8. We now know both the byte the first row of the sprite is in, and at which bit in that byte the sprite starts at. If the x coordinate is an exact multiple of eight, then there will be no need for shifting (the sprite is "aligned"). We jump to a part of the routine that specially handles the aligned case for speed.
LD C, A LD DE, 12The bit the sprite starts in is also the shift count, and we need it multiple times so we save it away. Then 12 is put into DE so as to move HL to the next screen row when we are finished with placing a row of the sprite.
_RowLoop: PUSH BC LD B, C LD C, (IX) XOR A _ShiftLoop: SRL C RRA DJNZ _ShiftLoopOkay, we are going to shift the sprite row across the A and C registers until it is in place. A note about the technique: by clearing A and using SRL, we guarantee that the empty bits in the two registers hold zeros. This was done to preserve the integrity of the other pixels, since XORing anything with zero causes no change.
INC HL XOR (HL) LD (HL), A DEC HL LD A, C XOR (HL) LD (HL), ANothing special, just putting both components of the sprite into the graph buffer.
ADD HL, DE INC IX POP BC DJNZ _RowLoop RETAnd now we move on to the next line of the sprite and video memory.
_Aligned: LD DE, 12 _PutLoop LD A, (IX) XOR (HL) LD (HL), A INC IX ADD HL, DE DJNZ _PutLoop RETThis is the code to special-case aligned sprites. Looks a lot simpler without all that shifting getting in the way, eh?
To use a mask with a sprite, use AND logic with the mask to clear a space for the sprite to go, then XOR/OR the sprite data as normal. So, the mask takes care of the white parts, the sprite takes care of the black parts, and the union of the two handles transparency.
By the way, the data for the mask is: 1s for white pixels, 0s for transparent or black pixels.
PutSpriteMask: ; Displays an 8x8 masked sprite ; A = x coordinate ; E = y coordinate ; IX = address of sprite ; IX + 8 = address of mask LD H, 0 LD D, H LD E, L ADD HL, HL ADD HL, DE ADD HL, HL ADD HL, HL LD E, A SRL E SRL E SRL E ADD HL, DE LD DE, PlotSScreen ADD HL, DE AND 7 JR Z, _Aligned LD C, A LD 8 _RowLoop: PUSH BC LD B, C LD D, (IX) LD A, (IX + 8) LD C, 0 LD E, C _ShiftLoop: SRL A RR C SRL D RR E DJNZ _ShiftLoopThe code is the same as PutSpriteXOR all the way up to _ShiftLoop. We need to accomodate shifting both a sprite and a mask row. At this point, the sprite is stored in D:E, and the mask is stored in A:C
CPL AND (HL) XOR D LD (HL), A INC HL LD A, C CPL AND (HL) XOR E LD (HL), AThe mask data thinks 0-pixels are transparent and 1-pixels are cleared, but AND logic works the other way around, so CPL is used to invert the mask. You might wonder why not just store the mask the right way, and that's a good point. But then we would have had to have shifted 1's into the mask, and there is no instruction (except for SLL) that will do that.
LD DE, 12 ADD HL, DE INC IX POP BC DJNZ _RowLoop RET _Aligned: LD DE, 12 _PutLoop LD A, (IX + 8) AND (HL) XOR (IX) LD (HL), A INC IX ADD HL, DE DJNZ _PutLoop RETI'm not going to bother explaining.
XORed sprites, by virtue of the logic of XOR, can be erased merely by being redrawn at the exact same place. But, the OR, AND, and masked sprite types completely destroy the background. There is just no computation you can perform that will bring it back. For these types of sprites, a copy of the background must be made.
Let's say we have an 8-pixel-by-n-pixel sprite that needs to be clipped. There are six things to look for:
; E = y-position ; B = rows ; HL = address of sprite LD A, E OR A JP M, ClipTopIs y negative? If so, then we should clip the top part. Back to this in a minute, now let's do bottom clipping
SUB 64 RET NCIs y >= 64? If so, the sprite is off-screen and so we should stop.
NEG CP B JR NC, VertClipDoneDoing -(A - 64) is the same as doing 64 - A, and gives the number of sprite rows that will be visible. If this number is >= the number of rows in the sprite, then clipping isn't necessary.
LD B, A JR VertClipDoneClipping the bottom is done by shrinking the height of the sprite so that only the topmost rows are displayed.
ClipTop: LD A, B NEG SUB E RET NCIs y <= -height? If so, the sprite is off the screen and so don't display it.
Now at this point we know we must clip the sprite at the top, and to do so we must do three things:
y = -1: A = (256-8) - (256-1) = 248 - 255 = -7
y = -2: A = (256-8) - (256-2) = 248 - 254 = -6
y = -3: A = (256-8) - (256-3) = 248 - 253 = -5
y = -4: A = (256-8) - (256-4) = 248 - 252 = -4
y = -5: A = (256-8) - (256-5) = 248 - 251 = -3
y = -6: A = (256-8) - (256-6) = 248 - 250 = -2
y = -7: A = (256-8) - (256-7) = 248 - 249 = -1
PUSH AF ADD A, B LD E, 0 LD B, E LD C, A ADD IX, BC POP AF ; Get the new height NEG LD B, A VertClipDone: ; Display the sprite here.
x = 89: 1 bits of the sprite will wrap; bitmask with %11111110
x = 90: 2 bits of the sprite will wrap; bitmask with %11111100
x = 91: 3 bits of the sprite will wrap; bitmask with %11111000
x = 92: 4 bits of the sprite will wrap; bitmask with %11110000
x = 93: 5 bits of the sprite will wrap; bitmask with %11100000
x = 94: 6 bits of the sprite will wrap; bitmask with %11000000
x = 95: 7 bits of the sprite will wrap; bitmask with %10000000
To figure out the bitmask, all we need to do for coding is
; Given that A is the x-coordinate AND 7 LD B, A LD A, %11111111 _CalcMask: ADD A, A DJNZ _CalcMask LD (clip_mask), A
For clipping on the left edge, we can again exploit the wraparound effect. If a negative x-coordinate is increased so that it is very close to the right edge of the screen, part will spill over, and the portion that should be clipped is displayed normally. The magic number to be added is 96, and the bitmask is found exactly the same way as was done for right clipping (the only difference, the mask must be inverted).
; Given that A is the x-coordinate, and E is the y-coordinate PUSH AF AND 7 LD B, A LD A, %11111111 _CalcMask: ADD A, A DJNZ _CalcMask CPL LD (clip_mask), A POP AF ADD A, 96 DEC E
b_call(_RunIndicOff) b_call(_GrBufClr) Show: CALL PutSpr b_call(_GrBufCpy) KeyLoop: b_call(_GetKey) CP kClear RET Z CP kUp JR Z, MoveUp CP kDown JR Z, MoveDown CP kLeft JR Z, MoveLeft CP kRight JR NZ, KeyLoop ; Move sprite right CALL PutSpr ; Erase sprite LD HL, xpos INC (HL) JR Show ; Draw sprite at new location MoveLeft: ; Move sprite left CALL PutSpr LD HL, xpos DEC (HL) JR Show MoveUp: ; Move sprite up CALL PutSpr LD HL, ypos DEC (HL) JR Show MoveDown: CALL PutSpr LD HL, ypos INC (HL) JR Show ypos: .DB 0 xpos: .DB 0 sprite: .DB %10000001 .DB %11000011 .DB %01100110 .DB %00111100 .DB %00111100 .DB %01100110 .DB %11000011 .DB %10000001 PutSpr: LD DE, (ypos) LD IX, sprite LD B, 8 ClipSprXOR: ; D = xpos ; E = ypos ; B = height ; IX = image address ; Start by doing vertical clipping LD A, %11111111 ; Reset clipping mask LD (clip_mask), A LD A, E ; If ypos is negative OR A ; try clipping the top JP M, ClipTop ; SUB 64 ; If ypos is >= 64 RET NC ; sprite is off-screen NEG ; If (64 - ypos) > height CP B ; don't need to clip JR NC, VertClipDone ; LD B, A ; Do bottom clipping by JR VertClipDone ; setting height to (64 - ypos) ClipTop: LD A, B ; If ypos <= -height NEG ; sprite is off-screen SUB E ; RET NC ; PUSH AF ADD A, B ; Get the number of clipped rows LD E, 0 ; Set ypos to 0 (top of screen) LD B, E ; Advance image data pointer LD C, A ; ADD IX, BC ; POP AF NEG ; Get the number of visible rows LD B, A ; and set as height VertClipDone: ; Now we're doing horizontal clipping LD C, 0 ; Reset correction factor LD A, D CP -7 ; If 0 > xpos >= -7 JR NC, ClipLeft ; clip the left side CP 96 ; If xpos >= 96 RET NC ; sprite is off-screen CP 89 ; If 0 <= xpos < 89 JR C, HorizClipDone ; don't need to clip ClipRight: AND 7 ; Determine the clipping mask LD C, A LD A, %11111111 FindRightMask: ADD A, A DEC C JR NZ, FindRightMask LD (clip_mask), A LD A, D JR HorizClipDone ClipLeft: AND 7 ; Determine the clipping mask LD C, A LD A, %11111111 FindLeftMask: ADD A, A DEC C JR NZ, FindLeftMask CPL LD (clip_mask), A LD A, D ADD A, 96 ; Set xpos so sprite will "spill over" LD C, 12 ; Set correction HorizClipDone: ; A = xpos ; E = ypos ; B = height ; IX = image address ; Now we can finally display the sprite. LD H, 0 LD D, H LD L, E ADD HL, HL ADD HL, DE ADD HL, HL ADD HL, HL LD E, A SRL E SRL E SRL E ADD HL, DE LD DE, PlotSScreen ADD HL, DE LD D, 0 ; Correct graph buffer address LD E, C ; if clipping the left side SBC HL, DE ; AND 7 JR Z, _Aligned LD C, A LD DE, 11 _RowLoop: PUSH BC LD B, C LD A, (clip_mask) ; Mask out the part of the sprite AND (IX) ; to be horizontally clipped LD C, 0 _ShiftLoop: SRL A RR C DJNZ _ShiftLoop XOR (HL) LD (HL), A INC HL LD A, C XOR (HL) LD (HL), A ADD HL, DE INC IX POP BC DJNZ _RowLoop RET _Aligned: LD DE, 12 _PutLoop: LD A, (IX) XOR (HL) LD (HL), A INC IX ADD HL, DE DJNZ _PutLoop RET clip_mask: .DB 0