; A test file for the sprites. Written in a hurry, don't use this as an example of how to code ;) ; ; Based on information gleaned from http://www.specnext.com/sprites/ and TBBlue ; ; Unless you change them, controls are: ; ; Z = left ; X = right ; N = up ; M = down ; ; 1 = X mirror on, Q = X mirror off ; 2 = Y mirror on, W = Y mirror off ; 3 = rotate on, E = rotate off ; 4 = sprites in border, R = no sprites in border ; ; Border colours show the sprite overload and sprite collision bits ; Red = 12 or more sprites on a line ; Blue = collision bit set ; Magenta = both zeusemulate "48K","ULA+" ; Set the model and enable ULA+ (No sprite support without ULA+ enabled!) AppFilename equ "SpriteTestA" ; What we're called (for file generation) ; Keys used in this test code, just change them here SpriteLeftKey equ "Z" ; SpriteRightKey equ "X" ; SpriteUpKey equ "N" ; SpriteDownKey equ "M" ; SpriteMirrorXKey equ "1" ; SpriteNonMirrorXKey equ "Q" ; SpriteMirrorYKey equ "2" ; SpriteNonMirrorYKey equ "W" ; SpriteRotateKey equ "3" ; SpriteNonRotateKey equ "E" ; SpriteBorderKey equ "4" ; SpriteNonBorderKey equ "R" ; ; Sprite I/O ports Sprite_Palette_Port equ $53 ; Sprite_Pattern_Port equ $55 ; Sprite_Sprite_Port equ $57 ; Sprite_Register_Port equ $243B ; Sprite_Value_Port equ $253B ; Sprite_Index_Port equ $303B ; Read for the two flags ; Start planting code here. (When generating a tape file we start saving from here) AppFirst equ $8000 ; Run this in uncontended memory org AppFirst ; Start of application AppEntry ld a,#0 ; Add a boxed sprite so we can see the full size ld hl,SpriteData ; call WriteSpritePattern ; ; Add the sword or whatever the hell it is ld a,#1 ; Add another sprite ld hl,SpriteData_Ex1 ; call WriteSpritePattern ; ; Add a pointer roughly like the example on the web ld a,#2 ; "Add another sprite, Carol" ld hl,SpriteData_Ex2 ; call WriteSpritePattern ; ; Corner sprites, these use x/y mirroring ; This macro is code intensive, coordinates are in screen graphics X/Y ; So 0,0 is the top-left corner of a sprite sitting on the top-left graphics pixel mAddSprite_SC(0, 0, 0, 0,false,false,false, true,1) mAddSprite_SC(1, 240, 0, 0, true,false,false, true,1) mAddSprite_SC(2, 0,176, 0,false, true,false, true,1) mAddSprite_SC(3, 240,176, 0, true, true,false, true,1) ; Add a line of sprites, use numbers 4 .. 16 for x=0 to 12 mAddSprite_SC(4+x, x*22-8,32, 0,false,false,false, true,1); Add some sprites next ; Put some sprites in the border mAddSprite_SC(20, 120,-16, 0, false, false,false, true,0); Add some sprites mAddSprite_SC(21, 120,192, 0, false, false,false, true,0); Add some sprites mAddSprite_SC(22, -16, 88, 0, false, false,false, true,0); Add some sprites mAddSprite_SC(23, 256, 88, 0, false, false,false, true,0); Add some sprites ; Enable the sprite hardware mEnableSprites(true) ; Turn them on, and on the border ; Draw lines to show the screen edges ld hl,zxpixeladdr(0,0) ; Top-left ld c,$FF ; Pattern call DrawHorizontal ; ld hl,zxpixeladdr(0,191) ; BottomLeft ld c,$FF ; Pattern call DrawHorizontal ; ld hl,zxpixeladdr(0,0) ; Top-left ld c,$80 ; Pattern call DrawVertical ; ld hl,zxpixeladdr(255,0) ; Top-Right ld c,$01 ; Pattern call DrawVertical ; ; Setup the attributes ld hl,zxattraddr(0,0) ; Top-Left ld de,zxattraddr(8,0) ; Next one (8 pixels along) ld (hl),$0F ; White on blue ld bc,$2FF ; ldir ; ; The display loop Loop mAddSprite_SC(8, (SpriteX), (SpriteY), 0,(SpriteMirrorX),(SpriteMirrorY),(SpriteRotate),true,2); Draw the cursor sprite halt ; Wait for a frame ; Read the collision/sprite overflow flag ld hl,250 ; 200 lines ld bc,Sprite_Index_Port ; Read the flags ; This wants to occur once per video line, roughly. Can't be arsed making it perfect. ShowLoop in a,(c) ; out ($FE),a ; Show them in the border loop 52 ; Waste some t-states. nop ; lend ; dec hl ; Dec the line counter ld a,l ; or h ; jp nz ShowLoop ; ; Drive the user interface ; Mirror, rotate and move mKeyTest(SpriteMirrorXKey,MirrorSpriteX) ; mKeyTest(SpriteNonMirrorXKey,NonMirrorSpriteX) ; mKeyTest(SpriteMirrorYKey,MirrorSpriteY) ; mKeyTest(SpriteNonMirrorYKey,NonMirrorSpriteX) ; mKeyTest(SpriteRotateKey,RotateSprite) ; mKeyTest(SpriteNonRotateKey,NonRotateSprite) ; mKeyTest(SpriteBorderKey,SpriteBorder) ; mKeyTest(SpriteNonBorderKey,NonSpriteBorder) ; ; Move the "cursor" mKeyTest(SpriteLeftKey,MoveSpriteLeft) ; mKeyTest(SpriteRightKey,MoveSpriteRight) ; mKeyTest(SpriteUpKey,MoveSpriteUp) ; mKeyTest(SpriteDownKey,MoveSpriteDown) ; jp Loop ; ; Test if a key is pressed and take action if it is. mKeyTest macro(key,action) ; ld bc,zeuskeyaddr(key); ; in a,(c) ; and zeuskeymask(key) ; call z action ; mend ; ; Action routines, which get called on key-press MoveSpriteRight ld hl,(SpriteX) ; inc hl ; ld a,h ; and $01 ; ld h,a ; ld (SpriteX),hl ; ret ; MoveSpriteLeft ld hl,(SpriteX) ; dec hl ; ld a,h ; and $01 ; ld h,a ; ld (SpriteX),hl ; ret ; MoveSpriteUp ld a,(SpriteY) ; dec a ; ld (SpriteY),a ; ret ; MoveSpriteDown ld a,(SpriteY) ; inc a ; ld (SpriteY),a ; ret ; MirrorSpriteX ld a,1 ; ld (SpriteMirrorX),a ; ret ; MirrorSpriteY ld a,1 ; ld (SpriteMirrorY),a ; ret ; NonMirrorSpriteX ld a,0 ; ld (SpriteMirrorX),a ; ret ; NonMirrorSpriteY ld a,0 ; ld (SpriteMirrorY),a ; ret ; RotateSprite ld a,1 ; ld (SpriteRotate),a ; ret ; NonRotateSprite ld a,0 ; ld (SpriteRotate),a ; ret ; SpriteBorder ld a,1 ; ld (SpriteBorderF),a ; mEnableSprites(true) ; Turn them on, and in the border ret ; NonSpriteBorder ld a,0 ; ld (SpriteBorderF),a ; mEnableSprites(false) ; Turn them on, but not in the border ret ; ; Draw a horizontal line DrawHorizontal ld b,32 ; DH_Lp ld (hl),c ; inc l ; djnz DH_Lp ; ret ; Done ; Draw a vertical line - messy. DrawVertical proc ; call Block ; call Block ; Block ld b,8 ; ld de,32-$800 ; DV_Lp loop 8 ; ld a,(hl) ; or c ; ld (hl),a ; inc h ; lend ; add hl,de ; djnz DV_Lp ; ld de,$700 ; add hl,de ; ret ; Done pend ; ; Add a sprite using screen coordinates, so 0,0 is top-left corner of sprite on top-left pixel of Spectrum graphics ; Only X,Y are dynamic. The other paarameters must be known at assembly time mAddSprite_SC macro(ID,u16X,u8Y,PaletteOffset,bMirrorX,bMirrorY,bRotate,bVisible,Pattern); ; Get X ld hl,u16X ; X ld de,32 ; add hl,de ; ld a,u8Y ; Y add a,32 ; ld e,a ; ld bc,Sprite_Index_Port ; Set the sprite index ld a,ID and $3F ; out (c),a ; (0 to 63) ld bc,Sprite_Sprite_Port ; out (c),l ; X (7..0) out (c),e ; Y ; Calc byte 2 OOOOXYRX ld e,PaletteOffset shl 4 ; ld a,bMirrorX ; or a ; jr z DontMirrorX ; set 3,e ; DontMirrorX ld a,bMirrorY ; or a ; jr z DontMirrorY ; set 2,e ; DontMirrorY ld a,bRotate ; or a ; jr z DontRotate ; set 1,e ; DontRotate ld a,h ; and %0000 0 0 0 1 ; X MSB or e ; out (c),a ; ; Assume visible ld e,Pattern and %0 0 111111 ; ld a,bVisible ; or a ; jr z DriveByte4 ; DriveByte4 ld e,(Pattern and %0 0 111111) or %1 0000000 ; out (c),e ; mend ; ; Turn the sprite hardware on mEnableSprites macro(bInBorder) ; ld bc,Sprite_Register_Port ; ld a,$15 ; Select TBBlue R21 out (c),a ; ld bc,Sprite_Value_Port ; if bInBorder ; ld a,$03 ; [0] Enable the sprite hardware [1] Draw sprites over border else ; ld a,$01 ; [0] Enable the sprite hardware [1] Draw sprites over border endif ; out (c),a ; mend ; ; Write the pixels for a single sprite, HL -> pixels, A=Number WriteSpritePattern proc ; push hl,de,bc ; ld bc,Sprite_Index_Port ; Set the sprite index out (c),a ; (0 to 63) ld a,0 ; Send 256 pixel bytes (16*16) ld d,#0 ; Counter ld bc,Sprite_Pattern_Port ; PixelLoop: ld e,(hl) ; inc hl ; out (c),e ; dec d ; jr nz PixelLoop ; pop bc,de,hl ; ret ; pend ; ; Our sprite cTR equ %111 000 11 ; Transparent cBK equ %000 000 00 ; Black cBL equ %000 000 11 ; Blue cRD equ %111 000 00 ; Red cOR equ %111 101 00 ; Orange SpriteData db cBL,cBL,cBL,cBL,cBL,cBL,cBL,cBL,cBL,cBL,cBL,cBL,cBL,cBL,cBL,cBL; db cBL,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cBL; db cBL,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cBL; db cBL,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cBL; db cBL,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cRD,cTR,cTR,cTR,cTR,cTR,cBL; db cBL,cTR,cRD,cRD,cTR,cTR,cTR,cTR,cTR,cTR,cRD,cTR,cTR,cTR,cTR,cBL; db cBL,cTR,cTR,cRD,cRD,cTR,cTR,cTR,cTR,cTR,cTR,cRD,cTR,cTR,cTR,cBL; db cBL,cTR,cTR,cRD,cRD,cRD,cRD,cRD,cRD,cRD,cRD,cRD,cRD,cTR,cTR,cBL; db cBL,cTR,cTR,cRD,cRD,cRD,cRD,cRD,cRD,cRD,cRD,cRD,cRD,cTR,cTR,cBL; db cBL,cTR,cTR,cRD,cRD,cTR,cTR,cTR,cTR,cTR,cTR,cRD,cTR,cTR,cTR,cBL; db cBL,cTR,cRD,cRD,cTR,cTR,cTR,cTR,cTR,cTR,cRD,cTR,cTR,cTR,cTR,cBL; db cBL,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cRD,cTR,cTR,cTR,cTR,cTR,cBL; db cBL,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cBL; db cBL,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cBL; db cBL,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cBL; db cBL,cBL,cBL,cBL,cBL,cBL,cBL,cBL,cBL,cBL,cBL,cBL,cBL,cBL,cBL,cBL; ; Dunno what this mess is supposed to be. SpriteData_Ex1 db $04,$04,$04,$04,$04,$04,$04,$E3,$E3,$E3,$E3,$E3,$E3,$E3,$E3,$E3; db $04,$FF,$FF,$FF,$FF,$FF,$04,$E3,$E3,$E3,$E3,$E3,$E3,$E3,$E3,$E3; db $04,$FF,$FB,$FB,$FB,$FF,$04,$E3,$E3,$E3,$E3,$E3,$E3,$E3,$E3,$E3; db $04,$FF,$FB,$F5,$F5,$FB,$FF,$04,$E3,$E3,$E3,$E3,$E3,$E3,$E3,$E3; db $04,$FF,$FB,$F5,$A8,$A8,$FB,$FF,$04,$E3,$E3,$E3,$E3,$E3,$E3,$E3; db $04,$FF,$FF,$FB,$A8,$44,$A8,$FB,$FF,$04,$E3,$E3,$E3,$E3,$E3,$E3; db $04,$04,$04,$FF,$FB,$A8,$44,$A8,$FB,$FF,$04,$E3,$E3,$E3,$E3,$E3; db $E3,$E3,$E3,$04,$FF,$FB,$A8,$44,$44,$FB,$FF,$04,$E3,$04,$E3,$E3; db $E3,$E3,$E3,$E3,$04,$FF,$FB,$44,$44,$44,$FB,$FF,$04,$4D,$04,$E3; db $E3,$E3,$E3,$E3,$E3,$04,$FF,$FB,$44,$44,$44,$44,$FA,$4D,$04,$E3; db $E3,$E3,$E3,$E3,$E3,$E3,$04,$FF,$FB,$44,$FF,$F5,$44,$04,$E3,$E3; db $E3,$E3,$E3,$E3,$E3,$E3,$E3,$04,$FF,$44,$F5,$A8,$04,$E3,$E3,$E3; db $E3,$E3,$E3,$E3,$E3,$E3,$E3,$E3,$04,$FA,$44,$04,$A8,$04,$E3,$E3; db $E3,$E3,$E3,$E3,$E3,$E3,$E3,$04,$4D,$4D,$04,$E3,$04,$F5,$04,$E3; db $E3,$E3,$E3,$E3,$E3,$E3,$E3,$E3,$04,$04,$E3,$E3,$E3,$04,$FA,$04; db $E3,$E3,$E3,$E3,$E3,$E3,$E3,$E3,$E3,$E3,$E3,$E3,$E3,$E3,$04,$04; ; A pointer SpriteData_Ex2 db cTR,cTR,cTR,cTR,cTR,cTR,cBK,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR; db cTR,cTR,cTR,cTR,cTR,cTR,cBK,cBK,cTR,cTR,cTR,cTR,cTR,cTR,cTR,cTR; db cTR,cTR,cTR,cTR,cTR,cTR,cBK,cOR,cBK,cTR,cTR,cTR,cTR,cTR,cTR,cTR; db cTR,cTR,cTR,cTR,cTR,cTR,cBK,cOR,cOR,cBK,cTR,cTR,cTR,cTR,cTR,cTR; db cTR,cTR,cTR,cTR,cTR,cTR,cBK,cOR,cBK,cTR,cTR,cTR,cTR,cTR,cTR,cTR; db cTR,cTR,cTR,cTR,cTR,cTR,cBK,cOR,cBK,cTR,cTR,cTR,cTR,cTR,cTR,cTR; db cTR,cTR,cTR,cTR,cTR,cTR,cBK,cOR,cBK,cTR,cTR,cTR,cTR,cTR,cTR,cTR; db cTR,cTR,cTR,cTR,cTR,cTR,cBK,cOR,cBK,cTR,cTR,cTR,cTR,cTR,cTR,cTR; db cTR,cTR,cTR,cTR,cTR,cTR,cBK,cOR,cBK,cTR,cTR,cTR,cTR,cTR,cTR,cTR; db cTR,cTR,cTR,cTR,cTR,cTR,cBK,cOR,cBK,cTR,cTR,cTR,cTR,cTR,cTR,cTR; db cTR,cTR,cTR,cTR,cTR,cTR,cBK,cOR,cBK,cTR,cTR,cTR,cTR,cTR,cTR,cTR; db cTR,cTR,cTR,cTR,cTR,cTR,cBK,cOR,cBK,cTR,cTR,cTR,cTR,cTR,cTR,cTR; db cTR,cTR,cTR,cTR,cTR,cTR,cBK,cOR,cBK,cTR,cTR,cTR,cTR,cTR,cTR,cTR; db cTR,cTR,cTR,cTR,cTR,cTR,cBK,cOR,cBK,cTR,cTR,cTR,cTR,cTR,cTR,cTR; db cTR,cTR,cTR,cTR,cTR,cTR,cBK,cOR,cBK,cTR,cTR,cTR,cTR,cTR,cTR,cTR; db cTR,cTR,cTR,cTR,cTR,cTR,cBK,cBK,cBK,cTR,cTR,cTR,cTR,cTR,cTR,cTR; ; Data SpriteX dw 128 ; SpriteY db 96 ; SpriteMirrorX db 0 ; SpriteMirrorY db 0 ; SpriteRotate db 0 ; SpriteBorderF db 1 ; ; Stop planting code after this. (When generating a tape file we save bytes below here) AppLast equ *-1 ; The last used byte's address ; Generate some useful debugging commands profile AppFirst,AppLast-AppFirst+1 ; Enable profiling for all the code ; Setup the emulation registers, so Zeus can emulate this code correctly Zeus_PC equ AppEntry ; Tell the emulator where to start Zeus_SP equ $FF46 ; Tell the emulator where to put the stack ; These generate some output files ; We'll load a loading screen containing the text import_bin "sprites.scr",$4000 ; Load a loading screen that has the menu text ; Now, also generate a tzx file using the loader output_tzx AppFilename+".tzx",AppFilename,"",AppFirst,AppLast-AppFirst,1,AppEntry ; A tap file using the loader output_tap AppFilename+".tap",AppFilename,"",AppFirst,AppLast-AppFirst,1,AppEntry ; A tzx file using the loader