FourSpriter Final (Slower) Version - britlion - 03-11-2010
This is the final version, unless I /really/ have to mess with it more.
MK III might be better for people that want more time between sprite updates to do things - but they might get flickery sprites. This version is quite a bit slower, because after a halt to sync to the screen it waits for the screen to be drawn, and then plays with the sprites. This is a hold of about 1.5 frames per update doing nothing at all.
A clever programmer could probably steal some of the "wait for the screen to draw" time for their own purposes, if they had code that ran in fixed time.
On the plus side, this version is rock solid flicker free. Which was the point of the exercise, after all.
NOTE: There's an addition to the original useage. It has testing code built into the functions so you shouldn't be able to break them. When you are happy that the program runs fine, put #DEFINE FINAL_CODE into the program early on, and it will remove the boundary checking code. (and thus be a little bit smaller).
Code: REM Most of this code is (c) Paul Fisher, 2010.
REM Thankyou to Apenao and the Mojon twins for producing what I here altered beyond recognition!
REM Free use is hereby given to anyone to do what they please with it, on the proviso they say thankyou to me on anything produced using it.
#DEFINE BLACK 0
#DEFINE BLUE 1
#DEFINE RED 2
#DEFINE MAGENTA 3
#DEFINE GREEN 4
#DEFINE CYAN 5
#DEFINE YELLOW 6
#DEFINE WHITE 7
#DEFINE TRANSPARENT 8
#DEFINE CONTRAST 9
#DEFINE TRUE 1
#DEFINE FALSE 0
SUB fspInitialize (n as uByte, udgA as uByte, udgB as uByte, udgC as uByte, udgD as uByte,x as uByte,y as uByte) 'Initialize Sprite: n (sprite number 0-3);a,b,c,d (UDG number 0-20,99 means the sprite won't be printed)
#ifndef FINAL_CODE
IF n>3 OR (udgA>20 AND udgA<>99) or udgB>20 or udgC>20 or udgD>20 then Print "Out Of Bounds Call in ";"fspInitialize" : stop : END IF : REM Bounds checking - so a bad function call doesn't poke all over memory.
#endif
DIM targetAddr as uInteger
targetAddr=@fspDataStart+(48*n)
fspErase()
POKE targetAddr,udgA
targetAddr=targetAddr+1
POKE targetAddr,udgB
targetAddr=targetAddr+1
POKE targetAddr,udgC
targetAddr=targetAddr+1
POKE targetAddr,udgD
targetAddr=targetAddr+1
POKE targetAddr,x
targetAddr=targetAddr+1
POKE targetAddr,y
targetAddr=targetAddr+1
POKE targetAddr,x
targetAddr=targetAddr+1
POKE targetAddr,y
fspBufferAndDraw()
RETURN
fspDataStart:
REM This Section contains Fourspriter data and code that is called by other routines.
ASM
;; 16x16 Sprites moving a character each frame
;; Copyleft 2009 The Mojon Twins.
;; Updated in 2010 by Paul Fisher
;; Uses UDGs (it reads the address from the system variables).
;; It moves up to 4 sprites, preserving the backgrounds.
;; Sprite control is done writing a certain number
;; in the Sprite's first UDG
;; If the first UDG is 99, it won't be printed
; Note that the sprites are initialized to disabled.
fspDataStartAsm: ; each block is 48 bytes long. [This comment originally said 40 bytes, and it's clearly 48]
;; Sprite 1
udgs1: defb 99,0,0,0 ; Four UDGs of the first sprite.
x_pos1: defb 0 ; X position in chars.
y_pos1: defb 0 ; Y position in chars.
cx_pos1: defb 0 ; Previous X position in chars.
cy_pos1: defb 0 ; Previous Y position in chars.
buffer1: defb 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ; Background Gfx buffer.
attrs1: defb 6,7,7,7 ; Sprite ATTRs
buffatrs1: defb 0,0,0,0 ; Background Attr's buffer
;; Sprite 2
udgs2: defb 99,0,0,0
x_pos2: defb 0
y_pos2: defb 0
cx_pos2: defb 0
cy_pos2: defb 0
buffer2: defb 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
attrs2: defb 7,7,7,7
buffatrs2: defb 0,0,0,0
;; Sprite 3
udgs3: defb 99,0,0,0
x_pos3: defb 0
y_pos3: defb 0
cx_pos3: defb 0
cy_pos3: defb 0
buffer3: defb 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
attrs3: defb 7,7,7,7
buffatrs3: defb 0,0,0,0
;; Sprite 4
udgs4: defb 99,0,0,0
x_pos4: defb 0
y_pos4: defb 0
cx_pos4: defb 0
cy_pos4: defb 0
buffer4: defb 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
attrs4: defb 7,7,7,7
buffatrs4: defb 0,0,0,0
fspCollisionDetect: defb 0,0,0 ; Sprite 0 with 1,2,3
defb 0,0 ; Sprite 1 with 2,3
defb 0 ; sprite 2 with 3
fspCopyToScreen:
call get_scr_address ; Returns (xpos, ypos) in HL
ld a, (de)
ld (hl), a
inc h
inc de
ld a, (de)
ld (hl), a
inc h
inc de
ld a, (de)
ld (hl), a
inc h
inc de
ld a, (de)
ld (hl), a
inc h
inc de
ld a, (de)
ld (hl), a
inc h
inc de
ld a, (de)
ld (hl), a
inc h
inc de
ld a, (de)
ld (hl), a
inc h
inc de
ld a, (de)
ld (hl), a
inc h
inc de
ret
fspCopyFromScreen:
call get_scr_address
ld a, (hl)
ld (de), a
inc h
inc de
ld a, (hl)
ld (de), a
inc h
inc de
ld a, (hl)
ld (de), a
inc h
inc de
ld a, (hl)
ld (de), a
inc h
inc de
ld a, (hl)
ld (de), a
inc h
inc de
ld a, (hl)
ld (de), a
inc h
inc de
ld a, (hl)
ld (de), a
inc h
inc de
ld a, (hl)
ld (de), a
inc h
inc de
ret
; This function returns the address into HL of the screen address
; b,c in character grid notation.
; Original code was extracted by BloodBaz
get_scr_address:
push de
ld a,b ; b=x
and 31
ld l,a
ld a,c ; c=y
ld d,a
and 24
add a,64
ld h,a
ld a,d
and 7
rrca
rrca
rrca
or l
ld l,a
pop de
ret
;; This function returns the memory address of the Character Position
;; b,c in the attribute screen memory.
;; Adapted from code by Jonathan Cauldwell.
get_attr_address:
ld a,c ;ypos
rrca
rrca
rrca ; Multiply by 32
ld l,a ; Pass to L
and 3 ; Mask with 00000011
add a,88 ; 88 * 256 = 22528 - start of attributes.
ld h,a ; Put it in the High Byte
ld a,l ; We get y value *32
and 224 ; Mask with 11100000
ld l,a ; Put it in L
ld a,b ; xpos
add a,l ; Add it to the Low byte
ld l,a ; Put it back in L, and we're done. HL=Address.
ret
;; Routine to save the background to the buffer
fspEraseAsm:
ld hl, fspDataStartAsm
EXX
ld b, 4
i4chars2:
EXX
;;
ld a, (hl)
cp 99
jr z, nxt2
;; A
LD BC,6
;EX DE,HL ; HL now points at the start sprite data instead
ADD HL,BC ; HL moved on to point to old x-y positions.
LD b,(HL) ; b=x
INC HL
LD c,(HL) ;c=y
INC HL
EX DE,HL ; Now it's DE that points into the data buffer.
; Draw first background character
call fspCopyToScreen
;; B
; xpos++
inc b
; Draw second background Character
call fspCopyToScreen
;; C
; xpos --
dec b
; ypos ++
inc c
; draw Third background Character
call fspCopyToScreen
;; D
; xpos++
inc b
; Draw Fourth background Character
call fspCopyToScreen
;; move to attr
inc de
inc de
inc de
inc de
;Set our position to top left corner again
; xpos --
dec b
; ypos --
dec c
call get_attr_address
EX DE,HL
;copyattrs
ld bc, 32
;First
LDI
;Second
LDI
; DE=DE+BC
EX DE,HL
add hl,bc
EX DE,HL
;Third
LDI
;Fourth
LDI
nxt2: EXX
djnz i4chars2
ret
fspBufferAndDrawAsm:
ld hl, fspDataStartAsm
EXX
ld b, 4
i4chars:
;push bc
EXX
;Active Sprite?
ld a, (hl)
cp 99
jr z, nxt1
;; A
LD BC,4
ADD HL,BC ; move past the udg part
;xpos
ld b,(hl)
inc hl
;ypos
ld c,(hl)
inc hl
inc hl ; move past the old co-ordinates
inc hl
EX de,hl
call fspCopyFromScreen
;; B
; xpos++
;ld hl, xpos
;inc (hl)
inc b
call fspCopyFromScreen
;; C
; xpos --
;ld hl, xpos
;dec (hl)
dec b
; ypos ++
;ld hl, ypos
;inc (hl)
inc c
call fspCopyFromScreen
;; D
; xpos++
;ld hl, xpos
;inc (hl)
inc b
call fspCopyFromScreen
;; Now we point to the ATTR buffer,adding 4:
inc de
inc de
inc de
inc de
;Put ourselves back at the top left corner.
; xpos --
dec b
; ypos --
dec c
call get_attr_address
ld bc, 32
;First Character
LDI ; bc was loaded with 32 instead of 31 to account for the BC-- this instruction has.
;Second Character
LDI
add hl, bc ; we can get away with this because BC++ as well as hl--
;Third Character
LDI
;Fourth Character
LDI
EX DE,HL ; back to an HL pointer.
nxt1: EXX
djnz i4chars
; Go straight into Painting new sprites for speed.
;; Print sprites routine
;; UDGs are labeled from 0 to 21. The addres of the UDG is:
;; *(23675) + 256 * *(23676) + 8 * N
fspDrawAsm:
ld hl, fspDataStartAsm ;
EXX
ld b, 4 ;
i4chars3:
;push bc
EXX
ld a, (hl)
cp 99
jr z, nxt3
;; Copy pointer "HL" to alt reg
push HL
exx
pop HL
exx
ld bc,4
add hl,bc
; Get xpos
LD b,(hl) ;x
inc hl
; Get ypos
ld c,(hl) ;y
inc hl
push HL ; de Save pointer.
EXX
LD A,(HL)
INC HL
EXX
call fspGetUdgAddr
;Draw the first character
call fspCopyToScreen
; xpos++
inc b
; Address of the graphics
EXX
LD A,(HL)
INC HL
EXX
call fspGetUdgAddr
; Draw the second Character
call fspCopyToScreen
; xpos--
dec b
; ypos ++
inc c
; Address of Graphics
EXX
LD A,(HL)
INC HL
EXX
call fspGetUdgAddr
; Draw the third character
call fspCopyToScreen
; xpos ++
inc b
; Address of next Graphic
EXX
LD A,(HL)
INC HL
EXX
call fspGetUdgAddr
; Draw the fourth Character
call fspCopyToScreen
pop hl ; Recover hl to point at the start of the pixel buffer
; hl = hl + 32
ld a,l
add a,34
ld l,a
jp nc, fspNoIncH
inc h ; hl now points to the attr data.
fspNoIncH:
EX DE,HL
;Reset position to top left.
; xpos --
dec b
; ypos --
dec c
;; attr
call get_attr_address
EX DE,HL
;copyattrs
ld bc, 32
;First
LDI
;Second
LDI
; DE=DE+BC
EX DE,HL
add hl,bc
EX DE,HL
;Third
LDI
;Fourth
LDI
LD BC,4
ADD HL,BC ; Move pointer to start of next block
nxt3: EXX
djnz i4chars3
ret
fspGetUdgAddr:
; finds the address of the UDG # in A
ld HL,(23675) ;HL points to the UDG space
rlca
rlca
rlca ; a = N * 8
ld d, 0
ld e, a
add hl, de
EX DE,HL
ret
fspUpdateAsm: ; This name will be used from ASM
; For each Sprite:
;; *(data + 6) = *(data + 4)
;; *(data + 7) = *(data + 5)
ld hl, fspDataStartAsm+4 ; Points to sprite 1
ld de, fspDataStartAsm+6
ldi
ldi
ld hl, fspDataStartAsm+4+48 ; Points to sprite 2
ld de, fspDataStartAsm+6+48
ldi
ldi
ld hl, fspDataStartAsm+4+48+48 ; Points to sprite 3
ld de, fspDataStartAsm+6+48+48
ldi
ldi
ld hl, fspDataStartAsm+4+48+48+48 ; Points to sprite 4
ld de, fspDataStartAsm+6+48+48+48
ldi
ldi
ret
END ASM
END SUB
SUB fspDisable (n as UByte)
#ifndef FINAL_CODE
IF n>3 then Print "Out Of Bounds Call in ";"fspDisable" : stop: END IF
#endif
fspErase()
POKE @fspDataStart+48*n,99
end SUB
SUB fspCoord (n as uByte, x as uByte, y as uByte) 'Set sprite coords: n (sprite number);x,y (vertical,horizontal coords)
#ifndef FINAL_CODE
IF n>3 then Print "Out Of Bounds Call in ";"fspCoord" : stop: END IF
#endif
DIM targetAddr as uInteger
targetAddr=@fspDataStart+4+48*n
POKE targetAddr,x
targetAddr=targetAddr+1
POKE targetAddr,y
rem targetAddr=targetAddr+1
rem POKE targetAddr,x
rem targetAddr=targetAddr+1
rem POKE targetAddr,y
END SUB
SUB fspAttrs (n as uByte, attra as uByte, attrb as uByte, attrc as uByte, attrd as uByte) 'Set sprite attrs: n (sprite number);a,b,c,d (UDG attrs)
#ifndef FINAL_CODE
IF n>3 then Print "Out Of Bounds Call in ";"fspAttrs" : stop: END IF
#endif
DIM targetAddr as uInteger
targetAddr=@fspDataStart+40+48*n
POKE targetAddr,attra
targetAddr=targetAddr+1
POKE targetAddr,attrb
targetAddr=targetAddr+1
POKE targetAddr,attrc
targetAddr=targetAddr+1
POKE targetAddr,attrd
END SUB
FUNCTION fspAttrByte(fspInk as uByte,fspPaper as uByte,fspBright as uByte, fspFlash as uByte) as uByte
#ifndef FINAL_CODE
if fspInk > 7 OR fspPaper > 7 OR fspBright > 1 or fspFlash >1 then Print "Out Of Bounds Call in ";"fspAttrByte" : stop: END IF
#endif
return (fspFlash shl 7) + (fspBright shl 6) + (fspPaper shl 3) + fspInk
END FUNCTION
SUB fspAttr(n as uByte,fspInk as uByte,fspPaper as uByte,fspBright as uByte, fspFlash as uByte)
#ifndef FINAL_CODE
if fspInk > 7 OR fspPaper > 7 OR fspBright > 1 or fspFlash >1 then Print "Out Of Bounds Call in ";"fspAttr" : stop: END IF
#endif
DIM attrByte as uByte
attrByte=fspAttrByte(fspInk,fspPaper,fspBright,fspFlash)
fspAttrs(n,attrByte,attrByte,attrByte,attrByte)
END SUB
SUB FASTCALL fspErase()
asm
call fspEraseAsm
end asm
END SUB
SUB FASTCALL fspBufferAndDraw()
asm
call fspBufferAndDrawAsm
end asm
END SUB
SUB FASTCALL fspUpdate()
asm
call fspUpdateAsm
END ASM
END SUB
SUB fspRedraw()
asm
halt
; Wait for scan line
LD BC,2120
fspRedrawloop:
DEC BC
LD A,B
OR C
JR NZ,fspRedrawloop
;REM Erase the sprites
call fspEraseAsm
;REM Save background and
;REM print sprites
call fspBufferAndDrawAsm
;REM update coordinates
call fspUpdateAsm
end asm
END SUB
Also, here is Apenao's Demonstration program, tweaked to be a little shinier.
Code: #include <sinclair.bas>
#include <keys.bas>
100 DIM gentle (0 to 3,0 to 7) AS uByte => { { 15, 15, 15, 15, 15, 15, 13, 15} , _
{ 240, 144, 208, 208, 240, 240, 176, 240} , _
{ 15, 14, 63, 0, 0, 12, 26, 30} , _
{ 176, 112, 252, 0, 0, 48, 104, 120}}
110 POKE Uinteger 23675, @gentle(0,0)
CLS
for n = 0 to 15
print paper (n mod 7); ink ((n+3) mod 8); "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456"
next n
let cx=1:let cy=1: let cmx=1:let cmy=1
let dx=7:let dy=5: let dmx=1:let dmy=-1
let tx=10:let ty=7: let tmx=-1:let tmy=1
LET gx=15:LET gy=11
fspInitialize (0,0,1,2,3,gx,gy) : REM (sprite number, udgA, udgB,udgC,udgD, initial X co-ordinate, initial y-coordinate)
fspInitialize (1,0,1,2,3,dx,dy)
fspInitialize (2,0,1,2,3,tx,ty)
fspInitialize (3,0,1,2,3,cx,cy)
fspAttr(0,WHITE,MAGENTA,FALSE,FALSE) : REM (sprite number, ink, paper, bright, flash)
fspAttr(1,YELLOW,BLACK,FALSE,FALSE)
fspAttr(2,GREEN,BLUE,TRUE,FALSE)
fspAttr(3,RED,WHITE,FALSE,FALSE)
DO
pause 1
IF MULTIKEYS(KEYO) and gx>0 THEN LET gx=gx-1: END IF
IF MULTIKEYS(KEYP) and gx<30 THEN LET gx=gx+1:END IF
IF MULTIKEYS(KEYQ) and gy>0 THEN LET gy=gy-1 :END IF
IF MULTIKEYS(KEYA) and gy<22 THEN LET gy=gy+1: END IF
let dx=dx+dmx:if dx=1 or dx=30 then let dmx=-dmx : END IF
let dy=dy+dmy:if dy=0 or dy=22 then let dmy=-dmy: END IF
let tx=tx+tmx:if tx=0 or tx=30 then let tmx=-tmx: END IF
let ty=ty+tmy:if ty=0 or ty=22 then let tmy=-tmy: END IF
let cx=cx+cmx:if cx=0 or cx=30 then let cmx=-cmx: END IF
let cy=cy+cmy:if cy=0 or cy=22 then let cmy=-cmy: END IF
fspCoord (0,gx,gy)
fspCoord (1,dx,dy)
fspCoord (2,tx,ty)
fspCoord (3,cx,cy)
fspRedraw()
loop
Re: FourSpriter Final (Slower) Version - slenkar - 10-29-2011
I tried to compile it but it complains about an underscore on line 20 of the 4spriter.bas
Re: FourSpriter Final (Slower) Version - britlion - 10-30-2011
slenkar Wrote:I tried to compile it but it complains about an underscore on line 20 of the 4spriter.bas
Yes - I'm having trouble compiling some older code of mine as well - I have perfectly working .tzx files for fourspriter, but either the code doesn't compile, or the code compiles to something that definitely behaves differently. I think the newest version of the compiler has introduced some more bugs, possibly?
Re: FourSpriter Final (Slower) Version - slenkar - 10-31-2011
can 4spriter be used with sprites that are 1 ATTR big or 2 or 3?
and what about different shapes, line a line of ATTR's
think of the tetris shapes
Re: FourSpriter Final (Slower) Version - britlion - 10-31-2011
slenkar Wrote:can 4spriter be used with sprites that are 1 ATTR big or 2 or 3?
and what about different shapes, line a line of ATTR's
think of the tetris shapes 
You'd have to change the design to allow for more than one attribute for a sprite.
And again, if you wanted to change the shape from 16 pixels x 16 pixels, you'd have to change the assembly quite a lot!
Re: FourSpriter Final (Slower) Version - slenkar - 10-31-2011
oh thats ok i just wondered,

would it require much change to get
8x16-character like manic miner
16x8-spaceship like R-Type
8x8 - bullets
Re: FourSpriter Final (Slower) Version - britlion - 10-31-2011
slenkar Wrote:oh thats ok i just wondered,

would it require much change to get
8x16-character like manic miner
16x8-spaceship like R-Type
8x8 - bullets
Since the sprite size is hard coded into the assembly section, it's not really trivial; though the size changes you're proposing are not too bad.
You might be better off using just part of the sprite space - er even being really clever and arranging some preshifted sprites, and changing the sprite graphics to allow your man to walk part of the way across the 16 pixel space - as he moves left, you change the sprite to make him look like he's headed left. Of course, you may be better off using the block put routines I developed for this sort of purpose instead.
You should read previous forum threads:
A 2x2 puttile routine, that paints a 2x2 thing to the screen pretty fast:
<!-- l --><a class="postlink-local" href="http://www.boriel.com/forum/how-to-tutorials/topic457.html">how-to-tutorials/topic457.html</a><!-- l -->
A putchars routine that paints any size graphic to the screen:
<!-- l --><a class="postlink-local" href="http://www.boriel.com/forum/how-to-tutorials/topic468.html">how-to-tutorials/topic468.html</a><!-- l -->
A pixel perfect print routine, which could be handy for your bullets at least - though a bit slow for general sprites:
<!-- l --><a class="postlink-local" href="http://www.boriel.com/forum/how-to-tutorials/topic493.html">how-to-tutorials/topic493.html</a><!-- l -->
Re: FourSpriter Final (Slower) Version - slenkar - 10-31-2011
highres print and putchars dont keep the background?
(just checking)
Re: FourSpriter Final (Slower) Version - britlion - 10-31-2011
Don't forget you can always cheat.
See if you can work out how this works:
Code: DIM sprite (320) AS uByte
let sprite (158)=%00010000
let sprite (159)=%00111000
dim f,n as uInteger
poke Uinteger 23675,@sprite(0)
cls
for f=0 to 20
print at f,9;CHR$(65+f)
next f
for n=0 to 152
poke Uinteger 23675,@sprite(n)
for f=0 to 20
print at f,10;CHR$(144+f)
next f
pause 10
next n
Re: FourSpriter Final (Slower) Version - britlion - 10-31-2011
slenkar Wrote:highres print and putchars dont keep the background?
(just checking)
No. You have options to use OVER type printing (OR with pixels already there), which means if you do it twice it goes back to the original, however. If you're using HRPrint to do more than a single character, you'll have to use OVER for the secondary characters so that they don't erase the first part you put down. See the example for details.
Re: FourSpriter Final (Slower) Version - slenkar - 10-31-2011
hmm not sure
Re: FourSpriter Final (Slower) Version - britlion - 10-31-2011
Pretty much everything except fourspriter is documented in the addon library, by the way:
<!-- m --><a class="postlink" href="http://www.boriel.com/wiki/en/index.php/ZX_BASIC:Library">http://www.boriel.com/wiki/en/index.php ... IC:Library</a><!-- m -->
|