Beginner's Sprite Tutorial

The place for codemasters or beginners to talk about programming any language for the Spectrum.
User avatar
R-Tape
Site Admin
Posts: 6353
Joined: Thu Nov 09, 2017 11:46 am

Beginner's Sprite Tutorial

Post by R-Tape »

After a few chats with Peter I promised to do a beginner's sprite tutorial, and it also follows another topic about whether beginner's asm is okay to avoid ROM calls. In this case there are 3 routines that are CALLed that the beginner does not need to worry about, just CALL it with the correct conditions (e.g. B=ycor C=xcor) and know what it RETurns with (e.g. DE pointing at the screen)

Could anyone just starting to learn asm let me know if it makes sense, and if not, which parts don't. Could anyone else suggest how it could be made clearer, or even better post an edited/update/totally improved example.

This assumes the beginner already knows how to assemble the code, and know the rudiments of the language.

I've got a feeling I'm assuming too much.
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 »

So this is a relatively simple and unoptimised routine to draw one sprite on the screen. For the purposes of this exercise, the routines nextlinedown, yx2pix and getsprite do not need to be understood yet.

In this example we draw the same sprite (a 16 x 16 pixel black square here) stored in memory, preshifted in all 8 possible positions, moving left to right, like this:

Image

So each position is 3 bytes wide and 16 pixels high, therefore each sprite is 48 bytes.

Here's the code:

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 bc,(xcor)		;make C=xcor and B=ycor, remember LD BC,(xcor) gets both xcor and ycor in one LD as they are adjacent in memory
	call yx2pix		;point DE at the corresponding screen address
	ld b,16			;sprite is 16 lines high
delp:	ld a,0			;empty A to delete
	ld (de),a		;repeat a total of 3 times
	inc e			;next column along
	ld (de),a
	inc e
	ld (de),a
	dec e
	dec e			;move DE back to start of line
	call nextlinedown	;move DE down one line
	djnz delp		;repeat 16 times
	ret
	;
movesprite:			;very simple routine that just increases the x coordinate
	ld a,(xcor)
	inc a
	ld (xcor),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 (xcor),a
	ret
	;
drawsprite:
	ld bc,(xcor)		;make C=xcor and B=ycor, remember LD BC,(xcor) gets both xcor and ycor in one LD as they are adjacent in memory
	call yx2pix		;point DE at corresponding screen position
	ld a,(xcor)		;but we still need to find which preshifted sprite to draw
	and 7			;we have 8 preshifted graphics to choose from, AND 7 isolates these as 0-7
	call getsprite		;point HL at the correct graphic
	ld b,16			;sprite is 16 lines high
dslp:	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
	inc e
	ld a,(hl)
	ld (de),a
	inc hl
	dec e
	dec e			;move DE back to left hand side of sprite
	call nextlinedown
	djnz dslp		;repeat for all 16 lines
	ret
	;
xcor:	db	0
ycor:	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!  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
	ld b,h
	ld c,l		;BC = x 16
	add hl,hl	;x32
	add hl,bc	;x48
	ld bc,spritegraphic
	add hl,bc	;HL now pointing at correct sprite frame
	ret
	;
spritegraphic:		;8 preshifted graphics, each one 3 bytes wide and 16 pixels high, this one a simple square
db 255, 255, 0		;frame 0
db 255, 255, 0
db 255, 255, 0
db 255, 255, 0
db 255, 255, 0
db 255, 255, 0
db 255, 255, 0
db 255, 255, 0
db 255, 255, 0
db 255, 255, 0
db 255, 255, 0
db 255, 255, 0
db 255, 255, 0
db 255, 255, 0
db 255, 255, 0
db 255, 255, 0
;
db 127, 255, 128	;frame 1
db 127, 255, 128
db 127, 255, 128
db 127, 255, 128
db 127, 255, 128
db 127, 255, 128
db 127, 255, 128
db 127, 255, 128
db 127, 255, 128
db 127, 255, 128
db 127, 255, 128
db 127, 255, 128
db 127, 255, 128
db 127, 255, 128
db 127, 255, 128
db 127, 255, 128
;
db 63, 255, 192		;frame 2
db 63, 255, 192
db 63, 255, 192
db 63, 255, 192
db 63, 255, 192
db 63, 255, 192
db 63, 255, 192
db 63, 255, 192
db 63, 255, 192
db 63, 255, 192
db 63, 255, 192
db 63, 255, 192
db 63, 255, 192
db 63, 255, 192
db 63, 255, 192
db 63, 255, 192
;
db 31, 255, 224		;frame 3
db 31, 255, 224
db 31, 255, 224
db 31, 255, 224
db 31, 255, 224
db 31, 255, 224
db 31, 255, 224
db 31, 255, 224
db 31, 255, 224
db 31, 255, 224
db 31, 255, 224
db 31, 255, 224
db 31, 255, 224
db 31, 255, 224
db 31, 255, 224
db 31, 255, 224
;
db 15, 255, 240		;frame 4
db 15, 255, 240
db 15, 255, 240
db 15, 255, 240
db 15, 255, 240
db 15, 255, 240
db 15, 255, 240
db 15, 255, 240
db 15, 255, 240
db 15, 255, 240
db 15, 255, 240
db 15, 255, 240
db 15, 255, 240
db 15, 255, 240
db 15, 255, 240
db 15, 255, 240
;
db 7, 255, 248		;frame 5
db 7, 255, 248
db 7, 255, 248
db 7, 255, 248
db 7, 255, 248
db 7, 255, 248
db 7, 255, 248
db 7, 255, 248
db 7, 255, 248
db 7, 255, 248
db 7, 255, 248
db 7, 255, 248
db 7, 255, 248
db 7, 255, 248
db 7, 255, 248
db 7, 255, 248
;
db 3, 255, 252		;frame 6
db 3, 255, 252
db 3, 255, 252
db 3, 255, 252
db 3, 255, 252
db 3, 255, 252
db 3, 255, 252
db 3, 255, 252
db 3, 255, 252
db 3, 255, 252
db 3, 255, 252
db 3, 255, 252
db 3, 255, 252
db 3, 255, 252
db 3, 255, 252
db 3, 255, 252
;
db 1, 255, 254		;frame 7
db 1, 255, 254
db 1, 255, 254
db 1, 255, 254
db 1, 255, 254
db 1, 255, 254
db 1, 255, 254
db 1, 255, 254
db 1, 255, 254
db 1, 255, 254
db 1, 255, 254
db 1, 255, 254
db 1, 255, 254
db 1, 255, 254
db 1, 255, 254
db 1, 255, 254
;
If you assemble the code, and RANDOMIZE USR 32768, you should see this:

Image

;
User avatar
bob_fossil
Manic Miner
Posts: 654
Joined: Mon Nov 13, 2017 6:09 pm

Re: Beginner's Sprite Tutorial

Post by bob_fossil »

In 'deletesprite' you're doing

Code: Select all

delp:	ld a,0			;empty A to delete
I thought 'xor a' was the faster way to set the accumulator to zero - if you're not bothered about flags?

Bob Fossil
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 »

It certainly is, and probably something a beginner learns very early on, but I thought it's best to keep to unoptimised principles.
User avatar
bob_fossil
Manic Miner
Posts: 654
Joined: Mon Nov 13, 2017 6:09 pm

Re: Beginner's Sprite Tutorial

Post by bob_fossil »

Wasn't sure where you were pitching this as you ask for improvements in the first post but then the posted source mention it's unoptimised. So apologies if I've made it more complicated for anyone starting from scratch. My z80 isn't brilliant but I picked that one up when I was learning many years ago and it's stuck.
User avatar
PeterJ
Site Admin
Posts: 6855
Joined: Thu Nov 09, 2017 7:19 pm
Location: Surrey, UK

Re: Beginner's Sprite Tutorial

Post by PeterJ »

R-Tape wrote: Sat Dec 23, 2017 10:44 pm It certainly is, and probably something a beginner learns very early on, but I thought it's best to keep to unoptimised principles.
It's pitched at a perfect level! Thanks.
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 »

No that's cool Bob, in fact if a beginner reads it and thinks 'ld a,0?' I can do better than that!" and modifies the code themselves then even better.
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 »

PeterJ wrote: Sat Dec 23, 2017 10:59 pm It's pitched at a perfect level! Thanks.
Cheers! Then soon we'll be ORing and masking sprites :D
User avatar
bob_fossil
Manic Miner
Posts: 654
Joined: Mon Nov 13, 2017 6:09 pm

Re: Beginner's Sprite Tutorial

Post by bob_fossil »

Perhaps in a later tutorial you could explain the significance of why you've ORGed the code to 32768 if the code could be assembled to anywhere. Also, maybe the bit masking opcodes would be clearer if you used the binary prefix to define the masking value, e.g,

Code: Select all

and %00000111 ; and bits 2,1,0  (7)

and %11100000 ; and bits 7,6,5 (224)
Putting numbers in is fine when you're happy with the concept of bit masks but if you're new to it, maybe this lets you see how you got there? Anyways, thanks for posting the tutorial.
User avatar
MatGubbins
Dynamite Dan
Posts: 1238
Joined: Mon Nov 13, 2017 11:45 am
Location: Kent, UK

Re: Beginner's Sprite Tutorial

Post by MatGubbins »

Very nicely done and written.
There will probably be a big HEX/DEC argument, but you've kept it simple with DEC, making things easy for a beginner to understand the values when adding and subtracting without being bogged down in converting numbers and making extra mistakes. As with Bob Fossil's post, mention the binary bit value for masking, just to show the how/where/why/what that number is doing.
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 »

Cheers Bob & Mat,

I agree about the masking, better in binary, but also a diagram explaining why the xcor cycles 0-7 and why we mask them would be a good idea.

I've still Xmas shopping to do, might not get round to it tomorrow.
User avatar
PeterJ
Site Admin
Posts: 6855
Joined: Thu Nov 09, 2017 7:19 pm
Location: Surrey, UK

Re: Beginner's Sprite Tutorial

Post by PeterJ »

I've still Xmas shopping to do, might not get round to it tomorrow.
Don't get anything too big for me Dave!
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 »

PeterJ wrote: Sun Dec 24, 2017 7:36 am Don't get anything too big for me Dave!
I hope you like those chocolates shaped liked shellfish, I've got a box left over from last year :-p
User avatar
MatGubbins
Dynamite Dan
Posts: 1238
Joined: Mon Nov 13, 2017 11:45 am
Location: Kent, UK

Re: Beginner's Sprite Tutorial

Post by MatGubbins »

R-Tape wrote: Sun Dec 24, 2017 3:55 pm
PeterJ wrote: Sun Dec 24, 2017 7:36 am Don't get anything too big for me Dave!
I hope you like those chocolates shaped liked shellfish, I've got a box left over from last year :-p
But they were given to you from someone else that had them left over from the year before that! They must be Vintage!
User avatar
bob_fossil
Manic Miner
Posts: 654
Joined: Mon Nov 13, 2017 6:09 pm

Re: Beginner's Sprite Tutorial

Post by bob_fossil »

Been playing around with R-Tape's posted source today and managed to knock this up with z88dk. The main loop is in C but the sprite erasing and drawing is all in assembly.

Image

All the graphics I've done so far have been basic block printing so having some example code to work from has been helpful to see how animated sprites works with the 'interesting' Spectrum screen layout - so thanks again for doing this!
Wall_Axe
Manic Miner
Posts: 492
Joined: Mon Nov 13, 2017 11:13 pm

Re: Beginner's Sprite Tutorial

Post by Wall_Axe »

how are you avoiding flickering?
also could you give a verbal description of the erasing, because an 8x8 sprite could be in 4 atrributes at once, are you basically erasing all 4 close attributes every time?
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 »

There's no flickering because the ULA draws the entire screen sequentially, 50 times a second. If, in between those 50th second points, you write different values into the display memory multiple times (blank, redraw, blank, redraw, etc), the ULA will just draw the final result of that when it comes to the next drawing time.

It’s a bit like a strobe light flashing 50 times a second. Someone runs fast, but the strobe doesn’t capture all the movement, so you see big jumps in between. The movement is still happening, but you only see snapshots at regular intervals.

Everyone is used to flickering in basic because printing happens an awful lot slower. Erasing a character and reprinting it wouldn't happen in the same 1/50th second frame. The ROM is trying to do a lot of whole bunch of operating system stuff in every frame, not just printing, and none of it is particularly optimised for speed.
Last edited by Seven.FFF on Sun Dec 24, 2017 6:27 pm, edited 3 times in total.
Robin Verhagen-Guest
SevenFFF / Threetwosevensixseven / colonel32
NXtel NXTP ESP Update ESP Reset CSpect Plugins
dfzx
Manic Miner
Posts: 673
Joined: Mon Nov 13, 2017 6:55 pm
Location: New Forest, UK
Contact:

Re: Beginner's Sprite Tutorial

Post by dfzx »

Use a hollow box graphic, not a filled one. That black smear going down your first image isn't at all clear. Maybe add a grid on 8 pixel boundaries so it's clearer that there's 3 bytes of width, not 2. It took me a while to work out why the bottom block of the graphic has a white line down the right edge.

You should probably define the term "preshifted" rather than just using it as if the reader is expected to know what it means.

The term "therefore each sprite is 48 bytes" might be better as "therefore each preshifted position of the sprite is 48 bytes".

Use more meaningful labels. I presume "xcor" is an abbreviation of "X coordinate"? Make it, say, "x_position". I don't know what "delp" might be an abbreviation of.

I don't think the LD BC,(xcor) is helpful at this level, even with the comment. Load B and C separately.


Otherwise, very good. I think a better graphic which more clearly describes how the preshifted sprite gets put into the bytes of the display would help. If someone hasn't grasped the concept of what you're doing I don't think reading that code will help them. Once they have grasped it, I think it should be clear.
Derek Fountain, author of the ZX Spectrum C Programmer's Getting Started Guide and various open source games, hardware and other projects, including an IF1 and ZX Microdrive emulator.
User avatar
Ast A. Moore
Rick Dangerous
Posts: 2640
Joined: Mon Nov 13, 2017 3:16 pm

Re: Beginner's Sprite Tutorial

Post by Ast A. Moore »

Wall_Axe wrote: Sun Dec 24, 2017 6:00 pm how are you avoiding flickering?
The routine takes very little time and is completed entirely while the ULA is drawing the top border. By the time the beam reaches the active screen area, all the drawing and redrawing had long been finished. Hence, no flickering.

If you introduce a delay after the HALT, you can make the sprite flicker, or disappear entirely:

Code: Select all

	ld bc,$220
wait	dec bc
	ld a,b
	or c
	jp nz,wait
Feel free to play around with the value passed into BC.

To visualize where the electron beam is during code execution, it’s a good idea to changed the border color for that period. Modify the main loop as shown below, and you’ll see what I mean.

Code: Select all

main:	halt		

	ld bc,$220
wait	dec bc
	ld a,b
	or c
	jp nz,wait
			
	xor a		;border black
	out (254),a

	call deletesprit
	call movesprite	
	call drawsprite	

	ld a,7		;border white
	out (254),a

	jr main	
Every man should plant a tree, build a house, and write a ZX Spectrum game.

Author of A Yankee in Iraq, a 50 fps shoot-’em-up—the first game to utilize the floating bus on the +2A/+3,
and zasm Z80 Assembler syntax highlighter.
Wall_Axe
Manic Miner
Posts: 492
Joined: Mon Nov 13, 2017 11:13 pm

Re: Beginner's Sprite Tutorial

Post by Wall_Axe »

ohhh theres a halt, so the sprites are drawn when the raster beam is at the top, ok thanks
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 »

dfzx wrote: Sun Dec 24, 2017 6:20 pm Use a hollow box graphic, not a filled one. That black smear going down your first image isn't at all clear. Maybe add a grid on 8 pixel boundaries so it's clearer that there's 3 bytes of width, not 2. It took me a while to work out why the bottom block of the graphic has a white line down the right edge.
Agreed, I was tempted to show a walking sprite but a hollow box it is.
dfzx wrote: Sun Dec 24, 2017 6:20 pm You should probably define the term "preshifted" rather than just using it as if the reader is expected to know what it means.
Yep, and compared to rotated.
dfzx wrote: Sun Dec 24, 2017 6:20 pm The term "therefore each sprite is 48 bytes" might be better as "therefore each preshifted position of the sprite is 48 bytes".
Yup.
dfzx wrote: Sun Dec 24, 2017 6:20 pm Use more meaningful labels. I presume "xcor" is an abbreviation of "X coordinate"? Make it, say, "x_position". I don't know what "delp" might be an abbreviation of.
I think most will get xcor as x coordinate, but no harm in hammering it home. "delp" is delete loop,, and would be better described as such (or loop1, loop2 etc), I fell into my regular shorthand there.
dfzx wrote: Sun Dec 24, 2017 6:20 pm I don't think the LD BC,(xcor) is helpful at this level, even with the comment. Load B and C separately.
I almost did that. Agreed.
dfzx wrote: Sun Dec 24, 2017 6:20 pm Otherwise, very good. I think a better graphic which more clearly describes how the preshifted sprite gets put into the bytes of the display would help. If someone hasn't grasped the concept of what you're doing I don't think reading that code will help them. Once they have grasped it, I think it should be clear.
Cheers.
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 »

bob_fossil wrote: Sun Dec 24, 2017 5:50 pm Been playing around with R-Tape's posted source today and managed to knock this up with z88dk. The main loop is in C but the sprite erasing and drawing is all in assembly.

Image

All the graphics I've done so far have been basic block printing so having some example code to work from has been helpful to see how animated sprites works with the 'interesting' Spectrum screen layout - so thanks again for doing this!
That warms me cockles bob!
User avatar
RMartins
Manic Miner
Posts: 776
Joined: Thu Nov 16, 2017 3:26 pm

Re: Beginner's Sprite Tutorial

Post by RMartins »

I believe the only thinkg missing, is a graphical schema of what is going on, before actually diving into the code.

People like to see an overview, that prepares them for what comes next. Makes it easier to grasp.

Also, the concept of animation, and how it works in consecutive frames, might be worth a small intro.
Last edited by RMartins on Mon Dec 25, 2017 9:42 pm, edited 1 time in total.
dfzx
Manic Miner
Posts: 673
Joined: Mon Nov 13, 2017 6:55 pm
Location: New Forest, UK
Contact:

Re: Beginner's Sprite Tutorial

Post by dfzx »

Having thought about it a bit more, what you could do is split it into 2 lessons. The first could use a sprite one pixel high, which would allow the focus to remain solely on how the horizontal preshifting works. Then make the second lesson add the other 15 rows of the sprite, which is what you have now.
Derek Fountain, author of the ZX Spectrum C Programmer's Getting Started Guide and various open source games, hardware and other projects, including an IF1 and ZX Microdrive emulator.
User avatar
RMartins
Manic Miner
Posts: 776
Joined: Thu Nov 16, 2017 3:26 pm

Re: Beginner's Sprite Tutorial

Post by RMartins »

Something that I would change/add, would be to split the several shifted sprite versions, into actual distinct sprites.
i.e. having the 8 sprite versions, in order in memory is an optimization that might not be clear for a beginner.

If instead, we had a simple table, with the address of all those 8 pre-shifted sprites, we can still mutiple A by 2, and the get the Sprite address from the table.

From a beginners perspective, it's easier to grasp that we have a table of sprites (8 in this case), and we get one from there using an index.
(Coincidently, it's also faster, but that is not the point here)

Getting the sprite we want by multiplying the index by 48 (sprite total length in bytes), and then add the base address, because they are in sequence in memory, is not immediate to understand for a beginner.
Post Reply