Beginner's Sprite Tutorial

The place for codemasters or beginners to talk about programming any language for the Spectrum.
User avatar
PeterJ
Site Admin
Posts: 6854
Joined: Thu Nov 09, 2017 7:19 pm
Location: Surrey, UK

Re: Beginner's Sprite Tutorial

Post by PeterJ »

[mention]Seven.FFF[/mention] apart from in your tutorial are there any documents that explains the configuration bits you put before the code?

I suppose I have always used Pasmo because you don't need all that extra stuff, but I should spend some time on Zeus especially as it has an.emulator built in..
User avatar
Seven.FFF
Manic Miner
Posts: 735
Joined: Sat Nov 25, 2017 10:50 pm
Location: USA

Re: Beginner's Sprite Tutorial

Post by Seven.FFF »

PeterJ wrote: Tue Mar 27, 2018 7:52 pm [mention]Seven.FFF[/mention] apart from in your tutorial are there any documents that explains the configuration bits you put before the code?
Yes, for sure. The documentation is distributed on a non-linear fashion between here and here.

For a quick start, if you go File >> New ZX Spectrum (48K) you get a commented template. But let's see. This is what I consider to be the minimum configuration for a new project:

Code: Select all

zeusemulate "48K"
The machine type and feature set to emulate. Corresponds to the options in the dropdown. "48K" or "128K' would be the primary machine choices. Things like "ULA" or "NOROM" would be typical features you could add as a comma-separated parameter. If you pick an option that doesn't exist, and assemble, it will tell you what the choices are.

A lot of the Zeus options can be specified in the UI or in code. Doing it in the UI is obviously simpler and more intuitive, but doing it in the code has the advantage of being able to open, assemble and run multiple projects without having to tweak settings.

Code: Select all

zoLogicOperatorsHighPri = false
If you're compiling pasmo code you'd want to set this to true, as pasmo it's own strange operator precedence rules. Having this not set to match the code you're assembling is a major cause of errors, so I always like to put this into my own projects so I reset back to a good known configuration.

This is also corresponds to one of the many checkboxes on the Config tab.

Code: Select all

zoSupportStringEscapes  = false
Just my personal preference; lets you write paths with single backslashes instead of double ones. You don't need to specify it at all, really.

Code: Select all

Zeus_PC  = $nnnn
Stack equ $nnnn
Tells the emulator what PC and SP should be, right at the entry point. Unlike most emulators, Zeus boot the machine and phantom-type LOAD "" command. Instead it loads the initial machine state into memory and magically makes your assembled bytes appear in memory at the correct place(s), before jumping to your entry point. To do this, at the very least it needs to know what PC and SP are.

Conceptially, it's more like what happens at the point you finish loading a .z80 or .sna shapshot. The entire state of the machine is restored, before jumping to the entry point.

There are also other directives to set the registers, interrupt mode and enabled state, current 128K page, etc.

Instead of Zeus_PC you can also use the pasmo syntax of END $nnnn at the end of your code. For pasmo, that is an instruction to put a RANDOMIZE USR nnnnn command in the basic loader; for Zeus, it's an instruction for the emulator where to put the entry point.

It might seem like a pointless distinction, but having code injected directly makes 128K development a breeze - literally all eight 16K pages (or in the case of the Spectrum Next, all 223 8K pages!!) are in memory and ready to use right from the getgo, without the user having to write code to export them and merge them with a multipage loader.

Code: Select all

org $nnnn
Same as almost every assembler. Tells the assembler what address to put the next instruction. Often people only have one org at the beginnning, but you're able to write things like

Code: Select all

org zeuspage(7)
which will assemble the following code to $2C000. $2C000-$2FFFF is the 24-bit address that 128K page 7 permanently lives at, in the Zeus assembler and emulator. Doing a standard address page by writing to I/O port $7FFD would page page 7 in at $C000-$FFFF as well as $2C000-$2FFFF. The CPU only knows about the addresses between $0000-$FFFF, only having got a 16-bit address bus. But Zeus has it all in there, and keeps track of it. Other emulators do as well, of course, but having programmatic control over everything in both the assembler and emulator is pretty sweet, espectially for RAD stuff.

Code: Select all

output_z80"..\bin\blah.z80", $0000, EntryPoint
output_sna"..\bin\blah.sna", $0000, EntryPoint
output_tap"..\bin\blah.tap", "LoaderProgramName", "Comment", StartAddress, BytesToSave, 2, EntryPoint
output_tzx "..\bin\blah.tzx", "LoaderProgramName", "Comment", StartAddress, BytesToSave, 2, EntryPoint
Makes standard snapshot or tape (mode 2) files.

Where this gets interesting is the 128K/sparse memory scatterloader (mode 3) tape files, for example:

Code: Select all

output_tap"..\bin\blah.tap", "LoaderProgramName", "Comment", StartAddressOfBlock, BytesOfBlockToSave, 3, EntryPoint, BorderColours
output_tap_block "..\bin\blah.tap",StartAddressOfSecondBlock, BytesOfSecondBlockToSave
output_tap_block "..\bin\blah.tap",StartAddressOfThirdBlock, BytesOfThirdBlockToSave
The block addresses are 24-bit, so specifying $2C000, $2000 for example would load the first half of 128K page 7 directly into page 7. It's handled automatically - all the end user sees is the tape loading, while Zeus's custom loader code would do the necessary paging and copying to stuff everything into the right place.

If there's a loading screen at $4000-$5AFF (loaded with something like this):

Code: Select all

import_bin "loading.scr", $4000
then that gets progressively loaded in linear order (compensating for screen thirds) during the load of the rest of the data, like Halls of the Things did back in the day.
Robin Verhagen-Guest
SevenFFF / Threetwosevensixseven / colonel32
NXtel NXTP ESP Update ESP Reset CSpect Plugins
Ralf
Rick Dangerous
Posts: 2279
Joined: Mon Nov 13, 2017 11:59 am
Location: Poland

Re: Beginner's Sprite Tutorial

Post by Ralf »

I believe we all need some good article about this Zeus ;)

It's seems that it became a serious player among crossassemblers quite recently.
It was always Sjasm or Pasmo before. Okay, maybe not always but last 10 years which seems like always ;)

I'm personally not up to date. When I hear Zeus I think about this thing from 1983 ;)
https://spectrumcomputing.co.uk/index.p ... 96&id=9010
User avatar
Seven.FFF
Manic Miner
Posts: 735
Joined: Sat Nov 25, 2017 10:50 pm
Location: USA

Re: Beginner's Sprite Tutorial

Post by Seven.FFF »

Just to give a little teaser about another one of my fav Zeus features, I have a USB cable running into to my Spectrum Next, which has an integrated 2Mbps serial port. At the click of a button, Zeus assembles code, loads it down the wire into memory, and executes at the entry point.

It has a command line interface, so by typing my own commands I can list memory, do pokes, write to ports, reset the machine, disassemble any address (including 128K pages that aren't even currently paged in) with the source lines next to them, import files from disk on the PC into memory, and export memory blocks to disk on the PC.

Pretty much what any emulator debugger does - except that none of the emulators for the Next are fully functional yet, and the bits that are functional don't behave the same as they do on the real hardware.

You could get this running on a real Spectrum with a serial or parallel interface too, pretty easily (with some input from Simon).
Robin Verhagen-Guest
SevenFFF / Threetwosevensixseven / colonel32
NXtel NXTP ESP Update ESP Reset CSpect Plugins
User avatar
PeterJ
Site Admin
Posts: 6854
Joined: Thu Nov 09, 2017 7:19 pm
Location: Surrey, UK

Re: Beginner's Sprite Tutorial

Post by PeterJ »

[mention]R-Tape[/mention]. Is there any chance you could do versions of your examples to cover up and down from here please:

viewtopic.php?f=6&t=2575
Freespirit
Microbot
Posts: 105
Joined: Wed Jul 01, 2020 2:33 pm

Re: Beginner's Sprite Tutorial

Post by Freespirit »

Thanks for the code to print a sprite. I've created my own sprite and added to the code. My issue is my sprite is only 2 bytes wide, I have changed everything to allow for this but there is something is the 'yx2pix' call that I think only allows for 3 bytes wide sprites. Does anyone know what the code change would be for just 2 bytes wide? It's a bit complicated for me to follow at the moment. My sprite does move along but only a character at a time, something in that call stops in doing pixel movement.
User avatar
R-Tape
Site Admin
Posts: 6353
Joined: Thu Nov 09, 2017 11:46 am

Re: Beginner's Sprite Tutorial

Post by R-Tape »

Freespirit wrote: Thu Jul 02, 2020 8:41 pm Thanks for the code to print a sprite. I've created my own sprite and added to the code. My issue is my sprite is only 2 bytes wide, I have changed everything to allow for this but there is something is the 'yx2pix' call that I think only allows for 3 bytes wide sprites. Does anyone know what the code change would be for just 2 bytes wide? It's a bit complicated for me to follow at the moment. My sprite does move along but only a character at a time, something in that call stops in doing pixel movement.
Cheers. It's good to see the code being useful.

"yx2pix" is not the problem, as it only points DE to the top left of the sprite, irrespective of its size.

I've had a look, and if you take the code from here, and change the following subroutines:

This deletes two bytes wide and 16 high.

Code: Select all

	;
deletesprite:			;we need to delete the old sprite before we draw the new one.  The sprite is 3 bytes wide & 16 pixels high
	ld a,(x_coordinate)		;make C=xcor and B=ycor
	ld c,a
	ld a,(y_coordinate)
	ld b,a
	call yx2pix		;point DE at the corresponding screen address
	ld b,16			;sprite is 16 lines high
deleteloop:			;NEW!
	ld a,0			;empty A to delete
	ld (de),a		;repeat a total of 3 times
	inc e			;next column along
	ld (de),a
	dec e			;NEW
	call nextlinedown	;move DE down one line
	djnz deleteloop		;repeat 16 times
	ret
	;
This prints two bytes wide and 16 high.

Code: Select all

	;
drawsprite:
	ld a,(x_coordinate)		;make C=xcor and B=ycor
	ld c,a
	ld a,(y_coordinate)
	ld b,a
	call yx2pix		;point DE at corresponding screen position
	ld a,(x_coordinate)	;but we still need to find which preshifted sprite to draw
	and 00000111b		;we have 8 preshifted graphics to choose from, cycled 0-7 in the right hand 3 bits of the x coordinate
	call getsprite		;point HL at the correct graphic
	ld b,16			;sprite is 16 lines high
drawloop:	;NEW
	ld a,(hl)		;take a byte of graphic
	ld (de),a		;and put it on the screen
	inc hl			;next byte of graphic
	inc e			;next column on screen
	ld a,(hl)		;repeat for 3 bytes across
	ld (de),a
	inc hl
	dec e
	call nextlinedown
	djnz drawloop		;repeat for all 16 lines
	ret
	;
This now finds the graphic, remember that a 2 byte sprite is 32 bytes, not 48, so we need to multiply by 32.

Code: Select all

	;
getsprite:		;don't worry much about how this works!  for an alternative method that
			;uses a table see 'getsprite_alternativemethod'
			;Arrive A holding which pixel within a byte (0-7), point HL at correct graphic
	ld h,0		;we need to multiply A by 48, do it in HL
	ld l,a
	add hl,hl	;x2
	add hl,hl	;x4
	add hl,hl	;x8
	add hl,hl	;x16
	add hl,hl	;x32
	ld bc,spritegraphic0
	add hl,bc	;HL now pointing at correct sprite frame
	ret
	;
And here's everything together, with a 2 byte sprite:

Code: Select all

org 32768			;we can ORG (or assemble) this code anywhere really
				;a beginner's, unoptimised sprite routine
main:	halt			;this stops the program until the Spectrum is about to refresh the TV screen
				;the HALT is important to avoid sprite flicker, and it slows down the program
	call deletesprite	;we need to delete the old position of the sprite
	call movesprite		;move the sprite! Could be based on player key input or baddy AI
	call drawsprite		;get correct preshifted graphic, and draw it on the screen
	jr main			;loop!
	;
deletesprite:			;we need to delete the old sprite before we draw the new one.  The sprite is 3 bytes wide & 16 pixels high
	ld a,(x_coordinate)		;make C=xcor and B=ycor
	ld c,a
	ld a,(y_coordinate)
	ld b,a
	call yx2pix		;point DE at the corresponding screen address
	ld b,16			;sprite is 16 lines high
deleteloop:			;NEW!
	ld a,0			;empty A to delete
	ld (de),a		;repeat a total of 3 times
	inc e			;next column along
	ld (de),a
	dec e			;NEW
	call nextlinedown	;move DE down one line
	djnz deleteloop		;repeat 16 times
	ret
	;
movesprite:			;very simple routine that just increases the x coordinate
	ld a,(x_coordinate)
	inc a
	ld (x_coordinate),a
	cp 232			;check if the sprite has moved all the way to the right (256-24)
	ret c			;return if not
	ld a,0			;if yes then back to left
	ld (x_coordinate),a
	ret
	;
drawsprite:
	ld a,(x_coordinate)		;make C=xcor and B=ycor
	ld c,a
	ld a,(y_coordinate)
	ld b,a
	call yx2pix		;point DE at corresponding screen position
	ld a,(x_coordinate)	;but we still need to find which preshifted sprite to draw
	and 00000111b		;we have 8 preshifted graphics to choose from, cycled 0-7 in the right hand 3 bits of the x coordinate
	call getsprite		;point HL at the correct graphic
	ld b,16			;sprite is 16 lines high
drawloop:	;NEW
	ld a,(hl)		;take a byte of graphic
	ld (de),a		;and put it on the screen
	inc hl			;next byte of graphic
	inc e			;next column on screen
	ld a,(hl)		;repeat for 3 bytes across
	ld (de),a
	inc hl
	dec e
	call nextlinedown
	djnz drawloop		;repeat for all 16 lines
	ret
	;
x_coordinate:	db	0
y_coordinate:	db	0
	;
nextlinedown:			;don't worry about how this works yet!
	inc d			;just arrive with DE in the display file
	ld a,d			;and it gets moved down one line
	and 7
	ret nz
	ld a,e
	add a,32
	ld e,a
	ret c
	ld a,d
	sub 8
	ld d,a
	ret
	;
yx2pix:		;don't worry about how this works yet! just arrive with arrive with B=y 0-192, C=x 0-255
	ld a,b	;return with DE at corresponding place on the screen
	rra
	rra
	rra
	and 24
	or 64
	ld d,a
	ld a,b
	and 7
	or d
	ld d,a
	ld a,b
	rla
	rla
	and 224
	ld e,a
	ld a,c
	rra
	rra
	rra
	and 31
	or e
	ld e,a
	ret
	;
getsprite:		;don't worry much about how this works!  for an alternative method that
			;uses a table see 'getsprite_alternativemethod'
			;Arrive A holding which pixel within a byte (0-7), point HL at correct graphic
	ld h,0		;we need to multiply A by 48, do it in HL
	ld l,a
	add hl,hl	;x2
	add hl,hl	;x4
	add hl,hl	;x8
	add hl,hl	;x16
	add hl,hl	;x32
	ld bc,spritegraphic0
	add hl,bc	;HL now pointing at correct sprite frame
	ret
	;
spritegraphic0:		;8 preshifted graphics, each one 3 bytes wide and 16 pixels high, this one a simple square
; ASM data file from a ZX-Paintbrush picture with 16 x 128 pixels (= 2 x 16 characters)

; block based output of pixel data - each block contains 16 x 16 pixels

; block at pixel position (0,0):

db 126, 0, 255, 0, 219, 0, 255, 0
db 189, 0, 195, 0, 255, 0, 126, 0
db 24, 0, 24, 0, 24, 0, 24, 0
db 24, 0, 36, 0, 66, 0, 129, 0

; block at pixel position (0,16):

db 63, 0, 127, 128, 109, 128, 127, 128
db 94, 128, 97, 128, 127, 128, 63, 0
db 12, 0, 12, 0, 12, 0, 12, 0
db 12, 0, 18, 0, 33, 0, 64, 128

; block at pixel position (0,32):

db 31, 128, 63, 192, 54, 192, 63, 192
db 47, 64, 48, 192, 63, 192, 31, 128
db 6, 0, 6, 0, 6, 0, 6, 0
db 6, 0, 9, 0, 16, 128, 32, 64

; block at pixel position (0,48):

db 15, 192, 31, 224, 27, 96, 31, 224
db 23, 160, 24, 96, 31, 224, 15, 192
db 3, 0, 3, 0, 3, 0, 3, 0
db 3, 0, 4, 128, 8, 64, 16, 32

; block at pixel position (0,64):

db 7, 224, 15, 240, 13, 176, 15, 240
db 11, 208, 12, 48, 15, 240, 7, 224
db 1, 128, 1, 128, 1, 128, 1, 128
db 1, 128, 2, 64, 4, 32, 8, 16

; block at pixel position (0,80):

db 3, 240, 7, 248, 6, 216, 7, 248
db 5, 232, 6, 24, 7, 248, 3, 240
db 0, 192, 0, 192, 0, 192, 0, 192
db 0, 192, 1, 32, 2, 16, 4, 8

; block at pixel position (0,96):

db 1, 248, 3, 252, 3, 108, 3, 252
db 2, 244, 3, 12, 3, 252, 1, 248
db 0, 96, 0, 96, 0, 96, 0, 96
db 0, 96, 0, 144, 1, 8, 2, 4

; block at pixel position (0,112):

db 0, 252, 1, 254, 1, 182, 1, 254
db 1, 122, 1, 134, 1, 254, 0, 252
db 0, 48, 0, 48, 0, 48, 0, 48
db 0, 48, 0, 72, 0, 132, 1, 2
	;
llewelyn
Manic Miner
Posts: 205
Joined: Thu Feb 22, 2018 3:27 pm
Location: virginias eastern shore
Contact:

Re: Beginner's Sprite Tutorial

Post by llewelyn »

Thanks RTape! FINALLY an explanation that I could at least follow, maybe not understand exactly but its revealing to me why Basic slows the machine down in execution. As far as the question 'did I assume too much?' well yes as far as I'm concerned because I have no idea how to assemble or compile or whatever its called but thats okay. One look at that listing was enough to show me that theres no way I could ever learn all those instruction codes and I don't have the need to use machine code but I'm grateful to you for a lucid explanation and who knows one day I may try it myself just out of curiousity and make my own sprite to do better than just crib your lesson. First explanation thats ever made any sense to me, a sort of Rosetta Stone!
Freespirit
Microbot
Posts: 105
Joined: Wed Jul 01, 2020 2:33 pm

Re: Beginner's Sprite Tutorial

Post by Freespirit »

Thanks Rtape it works perfectly. If I just have one block of graphics do you know why it's not moving smoothly? I just changed getsprite to point to the first set only.

Code: Select all

; ​This ​is ​a ​basic ​template ​file ​for ​writing ​48K ​Spectrum ​code.

AppFilename ​ ​ ​ ​equ ​"Rtape" ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​; ​What ​we're ​called ​(for ​file ​generation)

AppFirst ​ ​ ​ ​ ​ ​ ​equ ​$8000 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​; ​First ​byte ​of ​code ​(uncontended ​memory)

 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​zeusemulate ​"48K","ULA+" ​ ​ ​ ​ ​ ​ ​ ​; ​Set ​the ​model ​and ​enable ​ULA+


; ​Start ​planting ​code ​here. ​(When ​generating ​a ​tape ​file ​we ​start ​saving ​from ​here)

 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​org ​AppFirst ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​; ​Start ​of ​application

;AppEntry ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​halt
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;we ​can ​ORG ​(or ​assemble) ​this ​code ​anywhere ​really
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;a ​beginner's, ​unoptimised ​sprite ​routine
AppEntry ​: ​ ​ ​halt ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;this ​stops ​the ​program ​until ​the ​Spectrum ​is ​about ​to ​refresh ​the ​TV ​screen
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;the ​HALT ​is ​important ​to ​avoid ​sprite ​flicker, ​and ​it ​slows ​down ​the ​program
 ​ ​ ​ ​ ​ ​ ​ ​call ​deletesprite ​ ​ ​ ​ ​ ​ ​;we ​need ​to ​delete ​the ​old ​position ​of ​the ​sprite
 ​ ​ ​ ​ ​ ​ ​ ​call ​movesprite ​ ​ ​ ​ ​ ​ ​ ​ ​;move ​the ​sprite! ​Could ​be ​based ​on ​player ​key ​input ​or ​baddy ​AI
 ​ ​ ​ ​ ​ ​ ​ ​call ​drawsprite ​ ​ ​ ​ ​ ​ ​ ​ ​;get ​correct ​preshifted ​graphic, ​and ​draw ​it ​on ​the ​screen
 ​ ​ ​ ​ ​ ​ ​ ​jr ​AppEntry ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;loop!
 ​ ​ ​ ​ ​ ​ ​ ​;
deletesprite ​: ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;we ​need ​to ​delete ​the ​old ​sprite ​before ​we ​draw ​the ​new ​one. ​ ​The ​sprite ​is ​3 ​bytes ​wide ​& ​16 ​pixels ​high
 ​ ​ ​ ​ ​ ​ ​ ​ld ​a,(x_coordinate) ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;make ​C=xcor ​and ​B=ycor
 ​ ​ ​ ​ ​ ​ ​ ​ld ​c,a
 ​ ​ ​ ​ ​ ​ ​ ​ld ​a,(y_coordinate)
 ​ ​ ​ ​ ​ ​ ​ ​ld ​b,a
 ​ ​ ​ ​ ​ ​ ​ ​call ​yx2pix ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;point ​DE ​at ​the ​corresponding ​screen ​address
 ​ ​ ​ ​ ​ ​ ​ ​ld ​b,16 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;sprite ​is ​16 ​lines ​high
deleteloop ​: ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;NEW!
 ​ ​ ​ ​ ​ ​ ​ ​ld ​a,0 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;empty ​A ​to ​delete
 ​ ​ ​ ​ ​ ​ ​ ​ld ​(de),a ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;repeat ​a ​total ​of ​3 ​times
 ​ ​ ​ ​ ​ ​ ​ ​inc ​e ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;next ​column ​along
 ​ ​ ​ ​ ​ ​ ​ ​ld ​(de),a
 ​ ​ ​ ​ ​ ​ ​ ​dec ​e ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;NEW
 ​ ​ ​ ​ ​ ​ ​ ​call ​nextlinedown ​ ​ ​ ​ ​ ​ ​;move ​DE ​down ​one ​line
 ​ ​ ​ ​ ​ ​ ​ ​djnz ​deleteloop ​ ​ ​ ​ ​ ​ ​ ​ ​;repeat ​16 ​times
 ​ ​ ​ ​ ​ ​ ​ ​ret
 ​ ​ ​ ​ ​ ​ ​ ​;
movesprite ​: ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;very ​simple ​routine ​that ​just ​increases ​the ​x ​coordinate
 ​ ​ ​ ​ ​ ​ ​ ​ld ​a,(x_coordinate)
 ​ ​ ​ ​ ​ ​ ​ ​inc ​a
 ​ ​ ​ ​ ​ ​ ​ ​ld ​(x_coordinate),a
 ​ ​ ​ ​ ​ ​ ​ ​cp ​232 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;check ​if ​the ​sprite ​has ​moved ​all ​the ​way ​to ​the ​right ​(256-24)
 ​ ​ ​ ​ ​ ​ ​ ​ret ​c ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;return ​if ​not
 ​ ​ ​ ​ ​ ​ ​ ​ld ​a,0 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;if ​yes ​then ​back ​to ​left
 ​ ​ ​ ​ ​ ​ ​ ​ld ​(x_coordinate),a
 ​ ​ ​ ​ ​ ​ ​ ​ret
 ​ ​ ​ ​ ​ ​ ​ ​;
drawsprite ​:
 ​ ​ ​ ​ ​ ​ ​ ​ld ​a,(x_coordinate) ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;make ​C=xcor ​and ​B=ycor
 ​ ​ ​ ​ ​ ​ ​ ​ld ​c,a
 ​ ​ ​ ​ ​ ​ ​ ​ld ​a,(y_coordinate)
 ​ ​ ​ ​ ​ ​ ​ ​ld ​b,a
 ​ ​ ​ ​ ​ ​ ​ ​call ​yx2pix ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;point ​DE ​at ​corresponding ​screen ​position
 ​ ​ ​ ​ ​ ​ ​ ​ld ​a,(x_coordinate) ​ ​ ​ ​ ​;but ​we ​still ​need ​to ​find ​which ​preshifted ​sprite ​to ​draw
 ​ ​ ​ ​ ​ ​ ​ ​and ​00000111b ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;we ​have ​8 ​preshifted ​graphics ​to ​choose ​from, ​cycled ​0-7 ​in ​the ​right ​hand ​3 ​bits ​of ​the ​x ​coordinate
 ​ ​ ​ ​ ​ ​ ​ ​call ​getsprite ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;point ​HL ​at ​the ​correct ​graphic
 ​ ​ ​ ​ ​ ​ ​ ​ld ​b,16 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;sprite ​is ​16 ​lines ​high
drawloop ​: ​ ​ ​ ​ ​ ​ ​;NEW
 ​ ​ ​ ​ ​ ​ ​ ​ld ​a,(hl) ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;take ​a ​byte ​of ​graphic
 ​ ​ ​ ​ ​ ​ ​ ​ld ​(de),a ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;and ​put ​it ​on ​the ​screen
 ​ ​ ​ ​ ​ ​ ​ ​inc ​hl ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;next ​byte ​of ​graphic
 ​ ​ ​ ​ ​ ​ ​ ​inc ​e ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;next ​column ​on ​screen
 ​ ​ ​ ​ ​ ​ ​ ​ld ​a,(hl) ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;repeat ​for ​3 ​bytes ​across
 ​ ​ ​ ​ ​ ​ ​ ​ld ​(de),a
 ​ ​ ​ ​ ​ ​ ​ ​inc ​hl
 ​ ​ ​ ​ ​ ​ ​ ​dec ​e
 ​ ​ ​ ​ ​ ​ ​ ​call ​nextlinedown
 ​ ​ ​ ​ ​ ​ ​ ​djnz ​drawloop ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;repeat ​for ​all ​16 ​lines
 ​ ​ ​ ​ ​ ​ ​ ​ret
 ​ ​ ​ ​ ​ ​ ​ ​;
x_coordinate ​: ​ ​ ​db ​ ​ ​ ​ ​ ​0
y_coordinate ​: ​ ​ ​db ​ ​ ​ ​ ​ ​0
 ​ ​ ​ ​ ​ ​ ​ ​;
nextlinedown ​: ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;don't ​worry ​about ​how ​this ​works ​yet!
 ​ ​ ​ ​ ​ ​ ​ ​inc ​d ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;just ​arrive ​with ​DE ​in ​the ​display ​file
 ​ ​ ​ ​ ​ ​ ​ ​ld ​a,d ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;and ​it ​gets ​moved ​down ​one ​line
 ​ ​ ​ ​ ​ ​ ​ ​and ​7
 ​ ​ ​ ​ ​ ​ ​ ​ret ​nz
 ​ ​ ​ ​ ​ ​ ​ ​ld ​a,e
 ​ ​ ​ ​ ​ ​ ​ ​add ​a,32
 ​ ​ ​ ​ ​ ​ ​ ​ld ​e,a
 ​ ​ ​ ​ ​ ​ ​ ​ret ​c
 ​ ​ ​ ​ ​ ​ ​ ​ld ​a,d
 ​ ​ ​ ​ ​ ​ ​ ​sub ​8
 ​ ​ ​ ​ ​ ​ ​ ​ld ​d,a
 ​ ​ ​ ​ ​ ​ ​ ​ret
 ​ ​ ​ ​ ​ ​ ​ ​;
yx2pix ​: ​ ​ ​ ​ ​ ​ ​ ​ ​;don't ​worry ​about ​how ​this ​works ​yet! ​just ​arrive ​with ​arrive ​with ​B=y ​0-192, ​C=x ​0-255
 ​ ​ ​ ​ ​ ​ ​ ​ld ​a,b ​ ​;return ​with ​DE ​at ​corresponding ​place ​on ​the ​screen
 ​ ​ ​ ​ ​ ​ ​ ​rra
 ​ ​ ​ ​ ​ ​ ​ ​rra
 ​ ​ ​ ​ ​ ​ ​ ​rra
 ​ ​ ​ ​ ​ ​ ​ ​and ​24
 ​ ​ ​ ​ ​ ​ ​ ​or ​64
 ​ ​ ​ ​ ​ ​ ​ ​ld ​d,a
 ​ ​ ​ ​ ​ ​ ​ ​ld ​a,b
 ​ ​ ​ ​ ​ ​ ​ ​and ​7
 ​ ​ ​ ​ ​ ​ ​ ​or ​d
 ​ ​ ​ ​ ​ ​ ​ ​ld ​d,a
 ​ ​ ​ ​ ​ ​ ​ ​ld ​a,b
 ​ ​ ​ ​ ​ ​ ​ ​rla
 ​ ​ ​ ​ ​ ​ ​ ​rla
 ​ ​ ​ ​ ​ ​ ​ ​and ​224
 ​ ​ ​ ​ ​ ​ ​ ​ld ​e,a
 ​ ​ ​ ​ ​ ​ ​ ​ld ​a,c
 ​ ​ ​ ​ ​ ​ ​ ​rra
 ​ ​ ​ ​ ​ ​ ​ ​rra
 ​ ​ ​ ​ ​ ​ ​ ​rra
 ​ ​ ​ ​ ​ ​ ​ ​and ​31
 ​ ​ ​ ​ ​ ​ ​ ​or ​e
 ​ ​ ​ ​ ​ ​ ​ ​ld ​e,a
 ​ ​ ​ ​ ​ ​ ​ ​ret
 ​ ​ ​ ​ ​ ​ ​ ​;
getsprite ​: ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;don't ​worry ​much ​about ​how ​this ​works! ​ ​for ​an ​alternative ​method ​that
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;uses ​a ​table ​see ​'getsprite_alternativemethod'
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;Arrive ​A ​holding ​which ​pixel ​within ​a ​byte ​(0-7), ​point ​HL ​at ​correct ​graphic

 ​ ​ ​ ​ ​ ​ ​ ​ld ​bc,spritegraphic0
 ​ ​ ​ ​ ​ ​ ​ ​ld ​hl,bc ​ ​ ​ ​ ​ ​ ​;HL ​now ​pointing ​at ​correct ​sprite ​frame
 ​ ​ ​ ​ ​ ​ ​ ​ret
 ​ ​ ​ ​ ​ ​ ​ ​;
spritegraphic0 ​: ​ ​ ​ ​ ​ ​ ​ ​ ​;8 ​preshifted ​graphics, ​each ​one ​3 ​bytes ​wide ​and ​16 ​pixels ​high, ​this ​one ​a ​simple ​square
; ​ASM ​data ​file ​from ​a ​ZX-Paintbrush ​picture ​with ​16 ​x ​128 ​pixels ​(= ​2 ​x ​16 ​characters)

; ​block ​based ​output ​of ​pixel ​data ​- ​each ​block ​contains ​16 ​x ​16 ​pixels

; ​block ​at ​pixel ​position ​(0,0):

db ​126, ​0, ​255, ​0, ​219, ​0, ​255, ​0
db ​189, ​0, ​195, ​0, ​255, ​0, ​126, ​0
db ​24, ​0, ​24, ​0, ​24, ​0, ​24, ​0
db ​24, ​0, ​36, ​0, ​66, ​0, ​129, ​0


 ​ ​ ​ ​ ​ ​ ​ ​; ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​; ​Replace ​these ​lines ​with ​your ​code
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​jp ​AppEntry ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​;


; ​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 ​$FF40 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​; ​Tell ​the ​emulator ​where ​to ​put ​the ​stack

; ​These ​generate ​some ​output ​files

 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​; ​Generate ​a ​SZX ​file
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​output_szx ​AppFilename+".szx",$0000,AppEntry ​ ​ ​ ​; ​The ​szx ​file

 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​; ​If ​we ​want ​a ​fancy ​loader ​we ​need ​to ​load ​a ​loading ​screen
; ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​import_bin ​AppFilename+".scr",$4000 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​; ​Load ​a ​loading ​screen

 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​; ​Now, ​also ​generate ​a ​tzx ​file ​using ​the ​loader
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​output_tzx ​AppFilename+".tzx",AppFilename,"",AppFirst,AppLast-AppFirst,1,AppEntry ​; ​A ​tzx ​file ​using ​the ​loader


User avatar
R-Tape
Site Admin
Posts: 6353
Joined: Thu Nov 09, 2017 11:46 am

Re: Beginner's Sprite Tutorial

Post by R-Tape »

Freespirit wrote: Thu Jul 02, 2020 10:53 pm Thanks Rtape it works perfectly.
Bonzer.
If I just have one block of graphics do you know why it's not moving smoothly? I just changed getsprite to point to the first set only.
One block of graphic cannot move smoothly on the speccy—you need to store preshifted graphics.

In this case, you need to print the same thing 8 times, animated, in the same place, to give the illusion of smoothness. Each animation moves along by a pixel. See the pic below, for horizontal movement, you print a different animation at the same place 8 times, and THEN change the column. This is different to fancier computers like the C64, where you can simply draw the same thing at a different XY.

Image
Freespirit
Microbot
Posts: 105
Joined: Wed Jul 01, 2020 2:33 pm

Re: Beginner's Sprite Tutorial

Post by Freespirit »

Thanks for your great explanation R-Tape. All makes sense now. :D
myduis
Drutt
Posts: 2
Joined: Wed Apr 26, 2023 7:21 am

Re: Beginner's Sprite Tutorial

Post by myduis »

Hello everyone. This is my first approach to software sprites and my first attempts to write something for ZX. I have a question about the example programs for generating Sprite a few posts above - I checked and it works great.
The problem is that the code always "deletes" other elements of the screen when the sprite "passes" here - what needs to be done so that if I have a background element (or two sprites overlapping) they are not deleted but remain on the screen.
You need to somehow check new pixels whether they are set or not and when a new sprite movement is generated in this place, do not delete the already set background bits?
User avatar
PeterJ
Site Admin
Posts: 6854
Joined: Thu Nov 09, 2017 7:19 pm
Location: Surrey, UK

Re: Beginner's Sprite Tutorial

Post by PeterJ »

@myduis,

This book by Jonathan Cauldwell goes into software sprites in great detail:

https://jonathan-cauldwell.itch.io/how- ... trum-games

I'm mentioning @R-Tape as he created the thread, and will I'm sure be able to guide you in the right direction.
User avatar
R-Tape
Site Admin
Posts: 6353
Joined: Thu Nov 09, 2017 11:46 am

Re: Beginner's Sprite Tutorial

Post by R-Tape »

myduis wrote: Wed Apr 26, 2023 7:29 am The problem is that the code always "deletes" other elements of the screen when the sprite "passes" here - what needs to be done so that if I have a background element (or two sprites overlapping) they are not deleted but remain on the screen.
You need to somehow check new pixels whether they are set or not and when a new sprite movement is generated in this place, do not delete the already set background bits?
Hi @myduis. The example in this thread is one of the simpler ways of doing sprites (LD on and remove with empty space). It would be fine for a game with no background like The Pyramid, or a game with PAPER platforms like Loony Zoo.

Dealing with background is more complicated. There's a lot of ways to do it, but here's a quick summary of the two main methods (that I can think of):

-the XOR method. You XOR the sprite on and you XOR it off later. All AGD games use this method. Pros: simple and no need for a big screen buffer. Cons: pixel clash, and you can make a mess if you're not careful of the order things are drawn and deleted.

Code: Select all

;HL pointing at graphic
;DE pointing at screen

LD A,(DE) ;take a byte from the screen
XOR (HL) ;XOR the graphic onto it
LD (DE),A ;place the combination of background and graphic onto the screen

repeat...
-back buffer method. You keep a copy of the whole screen background in memory and you delete the players old position with data from there. You would usually OR the sprite on, or use masked sprites. Pros: sprites can interact with complex background and look perfect. Cons:the spare screen takes up a lot of memory.

Yep the book that Peter links to book goes into useful detail here.

It all depends on what kind of game you're making and how much experience you have. Could you give a bit more info?
myduis
Drutt
Posts: 2
Joined: Wed Apr 26, 2023 7:21 am

Re: Beginner's Sprite Tutorial

Post by myduis »

For now, I've made one very simple demo with jumping Willy using the method from the posts above (control keyboard WASD keys).
Thanks for the link to the document, it will help a lot.

TAPE:
https://dl.dropboxusercontent.com/s/r3x ... lytape.tap
CARTRIDGE:
https://dl.dropboxusercontent.com/s/3ry ... lycart.rom
User avatar
R-Tape
Site Admin
Posts: 6353
Joined: Thu Nov 09, 2017 11:46 am

Re: Beginner's Sprite Tutorial

Post by R-Tape »

myduis wrote: Wed Apr 26, 2023 10:55 am For now, I've made one very simple demo with jumping Willy using the method from the posts above (control keyboard WASD keys).
Thanks for the link to the document, it will help a lot.
That looks great, and you've already done loads of the hard work with the collision and background detection. I'd try out the XOR method.

You need to be very careful of the order of deletion and draw when using XOR, so I'd simplify the main loop to look something like this (and you should delete/draw every pass of the main loop). When you first start, you need to enter the main loop at 'entry', so you miss out the first 'xor_all_sprites_off' (otherwise you will be drawing then deleting and not seeing the sprites).

Code: Select all

main:	halt
	call xor_all_sprites_off
entry:	call xor_all_sprites_on
	;...
	;other game stuff
	;...	
	jr main
	;
And the sprite draw/delete routines could look something like this. You keep two address per sprite - one for the old screen address to delete, and the old graphic address that was XOR'd on.

Code: Select all

	;
xorsprite_off:
	ld de,(oldscadd)	;old screen address
	ld hl,(oldgraph)	;old graphic address
	call xorspr		;use the same code loop as xor on
	ret
	;
xorsprite_on:	;arrive HL at graphic, DE at screen
	ld (oldscadd),de	;store screen address for deletion later
	ld (oldgraph),hl	;store graphic address for deletion later
xorspr:	ld b,16
xslp:	ld a,(de)
	xor (hl)
	ld (de),a
	inc hl
	inc e
	ld a,(de)
	xor (hl)
	ld (de),a
	inc hl
	inc e
	ld a,(de)
	xor (hl)
	ld (de),a
	inc hl
	dec e
	dec e
	djnz xslp
	ret
	;
I don't have much time, so haven't tested that—I hope it makes sense, and works!
AndyC
Dynamite Dan
Posts: 1387
Joined: Mon Nov 13, 2017 5:12 am

Re: Beginner's Sprite Tutorial

Post by AndyC »

Ordering isn't too important if you use XOR, as long as you undraw/redraw everything so it may be better to do undraw/redrawn on an individual sprite basis to help reduce flickering.

Draw/erase order becomes much more important if you try to capture and restore sections of the background. It can be more efficient than having an entire back buffer, especially if you don't have many sprites, but requires you to restore the background in the opposite order to the one you drew in originally.
Post Reply