Understanding 6502 assembly on the Commodore 64 - (11) Sprite Movement and Collision

By now you've realized this series is not a quick and dirty instruction for cutting and pasting code allowing for instant gratification for displaying cool demos and raster effects on your screen.  There are a great many pages which demonstrate this.  This is about 6502 assembly, and the computers memory mapped archetecture.








Ive been doing my best to avoid NMI and IRQ.  So far so good.   I believe a quasi demonstration of an interrupt can be done without writing one.

In order to do this however, our sprite will need a friend.  I thought now would be a good time to build some bridges so our new sprite (SPRITE1) is an Apple, or my poor rendition of an apple.

Adding the second sprite is self explanatory,   but we will need some code to move our C64 logo about the screen.  Please note that I have moved the SPRITE0 data to a new location with a vector of
$80 and SPRITE1 to $81.  This would in fact allow my sprite loading loop to load both sprites into contiguous memory locations, but I keep it seperate for ease of following code.  In the future we will load Sprite data all at once, but not now

Ive cleaned up the code a bit too so its easier to follow along........ Here it is

; Jordans Super duper commodore logo maker, mover and shaker 
; 64TASS assembler style code for 6502     
; Jordan Rubin 2014 http://technocoma.blogspot.com

; start program type SYS 49152

*=$C000

;;;;;;WORDS ARE EASIER TO REMEMBER THAN ADDRESSES, LETS ASSIGN NAMES TO OUR ADRESSES


SPENA = $D015 ; Sprite Enable Register


SSDP0 = $07F8 ; Sprite Shape Data Pointers

SSDP1 = $07F9

SP0X = $D000 ; Sprite 0 Horizontal Position


SP0Y = $D001 ; Sprite 0 Vertical Position


SP1X = $D002 ; Sprite 1 Horizontal Position


SP1Y = $D003 ; Sprite 1 Vertical Position


MSIGX = $D010 ; Most Significant Bits of Sprites 0-7 Horizontal Position


YXPAND = $D017 ; Sprite Vertical Expansion Register


XXPAND = $D01D ; Sprite Horizontal Expansion Register


SP0COL = $D027 ; Sprite 0 Color Register


SP1COL = $D028 ; Sprite 1 Color Register


SPMC = $D01C ; Sprite Multicolor Registers


SP0DATA = $2000 ; Sprite 0 and 1 data start

 
   SP1DATA = $2040

CHROUT = $FFD2 ; Character out

SCNKEY = $FF9F ; Scan keyboard for keypress

GETIN = $FFE4 ; Get Input



START:

lda #$93
jsr CHROUT ; Load 147, hex $93 into A reg and JSR to CHROUT (Clears the screen)

lda #% 00000011

sta SPENA ; Activate SPRITE 0,1

lda #$90

sta SP0X ; Set X coordinate for SPRITE0  

lda #$B1

sta SP1X ; Set X coordinate for SPRITE1

lda #$8F

sta SP0Y ; Set Y coordinate for SPRITE0

lda #$8F

sta SP1Y ; Set Y coordinate for SPRITE1

lda #$80

sta SSDP0 ; Set the location to find SPRITE0 Data

lda #$81 

sta SSDP1 ; Set the location to find SPRITE0 Data

SP0INIT:

ldx #$00 ; SET X=0
jsr SPR0LOADLOOP ; Load SPRITE0 into memory
jsr SPR1LOADLOOP ; Load SPRITE1 into memory
jmp SCANKBD ; Scan Keyboard for a keypress
rts


;;;;;;;;;;;;;;;;;;;; This is a very quick and easy loader to move the sprite data

;;;;;;;;;;;;;;;;;;;; Into memory area SP0DATA, reading each byte and incrementing
;;;;;;;;;;;;;;;;;;;; the address until weve reached all 64 bytes, hex $40

SPR0LOADLOOP:

lda SPRITE0DATA,x
sta SP0DATA,x
inx
cpx #$40
bne SPR0LOADLOOP
rts

SPR1LOADLOOP:

lda SPRITE1DATA,x
sta SP1DATA,x
inx
cpx #$40
bne SPR1LOADLOOP
rts

;;;;;;;;;;;;;;;;  Our scan keyboard function, if a key is pressed, it will appear

;;;;;;;;;;;;;;;;  In the accumulator, hex value
SCANKBD:
jsr SCNKEY
jsr GETIN 


;;;;;;;;;;;;;;;  Check which key was pressed and JMP to its routine

TEST:
cmp #67
beq COLORCHANGE
cmp #69
beq END    
jmp SCANKBD

;;;;;;;;;;;;;;;  Incriment color value on keystroke C

COLORCHANGE:
ldy SP0COL
iny
sty SP0COL
jmp SCANKBD

;;;;;;;;;;;;;;;  Cause program to end on keystroke E

END:
ldy #% 00000000
sty SPENA
rts

; DUMP OUT SPRITE DATA INTO OUR AREA SP0DATA and SP1DATA

SPRITE0DATA:
.byte $03, $FF, $00, $07, $FF, $80, $0F, $FF, $80, $1F, $83, $80, $3E, $00, $80
.byte $7C, $00, $7F, $78, $00, $7E, $F8, $00, $7C, $F0, $00, $78, $F0, $00, $70 
.byte $F0, $00, $00, $F0, $00, $70, $F0, $00, $78, $F8, $00, $7C, $78, $00, $7E 
.byte $7C, $00, $7F, $3E, $00, $80, $1F, $83, $80, $0F, $FF, $80, $07, $FF, $80 
.byte $03, $FF, $00, $00                         
SPRITE1DATA:                   
.byte $02, $02, $00, $00, $88, $00, $00, $AA, $00, $0A, $AA, $80, $0A, $AA, $A0
.byte $2A, $AA, $A0, $2A, $AA, $A8, $2A, $AA, $A8, $2A, $AA, $A0, $2A, $AA, $80
.byte $2A, $AA, $80, $2A, $AA, $80, $2A, $AA, $80, $2A, $AA, $A0, $2A, $AA, $A8
.byte $2A, $AA, $A8, $0A, $AA, $A0, $0A, $AA, $A0, $02, $AA, $A0, $00, $AA, $80
.byte $00, $A2, $00, $82                                                            


This is what we have now........ Stop laughing at my Applesprite!!!!!!




We are going to need 4 new keyboard functions to move our commodore logo, the apple will remain stationary,  we also want something to happen when a collision is detected, maybe the apple turns green when they collide

So I created some new code in test to add functionality
UP is I, DOWN is K, LEFT is J, and RIGHT is L


;;;;;;;;;;;;;;;  Check which key was pressed and JMP to its routine
TEST:
cmp #67
beq COLORCHANGE
cmp #69
beq END    
cmp #73
beq UP
cmp #75
beq DOWN
cmp #74
beq LEFT
cmp #76
beq RIGHT
jmp SCANKBD


Now I need to create the corresponding labels and functions for UP DOWN LEFT RIGHT

UP:
ldy SP0Y
dey
sty SP0Y
jmp SCANKBD

DOWN:

ldy SP0Y
iny
sty SP0Y
jmp SCANKBD

LEFT:

ldy SP0X
dey
sty SP0X
jmp SCANKBD

RIGHT:

ldy SP0X
iny
sty SP0X
jmp SCANKBD

Too easy! we already know and have used SP0X and SP0Y in our program before, now its just a matter if incrementing and decrementing.  Note that for ease of code, I have nothing that checks to see if the sprite is at the end of the screen or if its too far right to move any further past FF and will loop back to 00.

What we need now, is with each keystroke and movement, some way to test if there is a collision with sprite 1, and if so, what to do?


Well, we always come back to SCANKBD so why not a Jump to SubRoutine there to check for a collision and handle it appropriately

;;;;;;;;;;;;;;;;  Our scan keyboard function, if a key is pressed, it will appear
;;;;;;;;;;;;;;;;  In the accumulator, hex value
SCANKBD:
jsr SCNKEY
jsr GETIN 
jsr COLLISION 

This would be a bad idea.   Why would we check for a collision over and over whilst waiting in our scan keyboard loop, why not check for a collision after a keypress, since the status isn't going to change until after the sprite is moved.



Lets put it here at the bottom to TEST just before we go back to scanning the keyboard

TEST:
cmp #67
beq COLORCHANGE
cmp #69
beq END    
cmp #73
beq UP
cmp #75
beq DOWN
cmp #74
beq LEFT
cmp #76
beq RIGHT
jsr COLLISION     PUT IT HERE
jmp SCANKBD


Lets now take a moment and look at our trusty memory map, looks like well need to assign $D01E the name of SPSPCL up top in our program


SPSPCL  = $D01E ; Sprite collision register


53278         $D01E          SPSPCL
Sprite to Sprite Collision Register

Bit 0:  Did Sprite 0 collide with another sprite?  (1=yes)
Bit 1:  Did Sprite 1 collide with another sprite?  (1=yes)
Bit 2:  Did Sprite 2 collide with another sprite?  (1=yes)
Bit 3:  Did Sprite 3 collide with another sprite?  (1=yes)
Bit 4:  Did Sprite 4 collide with another sprite?  (1=yes)
Bit 5:  Did Sprite 5 collide with another sprite?  (1=yes)
Bit 6:  Did Sprite 6 collide with another sprite?  (1=yes)
Bit 7:  Did Sprite 7 collide with another sprite?  (1=yes)

Its a good thing we learned how to express a value as binary, we will be using it here, with 2 sprites this will be simple.  If there is no sprite collision the value of D01E will be 00000000, if sprite0 collides with sprite 1, bits 0 and 1 will be high.  so the value of D01E will be 00000011 as shown


COLLISION:
ldy SPSPCL
cpy #% 00000011            ; Did Sprite0 and Sprite1 collide?
beq APPLEGREEN        ; if YES goto APPLEGREEN function
rts

APPLEGREEN:

lda #$F5
sta SP1COL    ; turn apple color green
rts


Here it is in action!!!!




Lets look at the final program, our controls are

E - END
C - Color cycle the C64 logo
I  - UP
K - DOWN
J - LEFT
L - RIGHT








; Jordans Super duper commodore logo maker, mover and shaker 

; 64TASS assembler style code for 6502     
; Jordan Rubin 2014 http://technocoma.blogspot.com

; start program type SYS 49152

*=$C000

;;;;;;WORDS ARE EASIER TO REMEMBER THAN ADDRESSES, LETS ASSIGN NAMES TO OUR ADRESSES


SPENA = $D015 ; Sprite Enable Register


SSDP0 = $07F8 ; Sprite Shape Data Pointers

SSDP1 = $07F9

SP0X = $D000 ; Sprite 0 Horizontal Position


SP0Y = $D001 ; Sprite 0 Vertical Position


SP1X = $D002 ; Sprite 1 Horizontal Position


SP1Y = $D003 ; Sprite 1 Vertical Position


MSIGX = $D010 ; Most Significant Bits of Sprites 0-7 Horizontal Position


YXPAND = $D017 ; Sprite Vertical Expansion Register


XXPAND = $D01D ; Sprite Horizontal Expansion Register


SP0COL = $D027 ; Sprite 0 Color Register


SP1COL = $D028 ; Sprite 1 Color Register


SPMC = $D01C ; Sprite Multicolor Registers


SP0DATA = $2000 ; Sprite 0 and 1 data start

SP1DATA = $2040

SPSPCL  = $D01E ; Sprite collosion register


CHROUT = $FFD2 ; Character out

SCNKEY = $FF9F ; Scan keyboard for keypress
GETIN = $FFE4 ; Get Input



START:

lda #$93
jsr CHROUT ; Load 147, hex $93 into A reg and JSR to CHROUT (Clears the screen)

lda #% 00000011

sta SPENA ; Activate SPRITE 0,1

lda #$90

sta SP0X ; Set X coordinate for SPRITE0  

lda #$B1

sta SP1X ; Set X coordinate for SPRITE1

lda #$8F

sta SP0Y ; Set Y coordinate for SPRITE0

lda #$8F

sta SP1Y ; Set Y coordinate for SPRITE1

lda #$80

sta SSDP0 ; Set the location to find SPRITE0 Data

lda #$81 

sta SSDP1 ; Set the location to find SPRITE0 Data

SP0INIT:

ldx #$00 ; SET X=0
jsr SPR0LOADLOOP ; Load SPRITE0 into memory
jsr SPR1LOADLOOP ; Load SPRITE1 into memory
jmp SCANKBD ; Scan Keyboard for a keypress
rts


;;;;;;;;;;;;;;;;;;;; This is a very quick and easy loader to move the sprite data

;;;;;;;;;;;;;;;;;;;; Into memory area SP0DATA, reading each byte and incrementing
;;;;;;;;;;;;;;;;;;;; the address until weve reached all 64 bytes, hex $40

SPR0LOADLOOP:

lda SPRITE0DATA,x
sta SP0DATA,x
inx
cpx #$40
bne SPR0LOADLOOP
rts

SPR1LOADLOOP:

lda SPRITE1DATA,x
sta SP1DATA,x
inx
cpx #$40
bne SPR1LOADLOOP
rts

;;;;;;;;;;;;;;;;  Our scan keyboard function, if a key is pressed, it will appear

;;;;;;;;;;;;;;;;  In the accumulator, hex value
SCANKBD:
jsr SCNKEY
jsr GETIN 

;;;;;;;;;;;;;;;  Check which key was pressed and JMP to its routine

TEST:
cmp #67
beq COLORCHANGE
cmp #69
beq END    
cmp #73
beq UP
cmp #75
beq DOWN
cmp #74
beq LEFT
cmp #76
beq RIGHT
jsr COLLISION
jmp SCANKBD

UP:

ldy SP0Y
dey
sty SP0Y
jmp SCANKBD

DOWN:

ldy SP0Y
iny
sty SP0Y
jmp SCANKBD

LEFT:

ldy SP0X
dey
sty SP0X
jmp SCANKBD

RIGHT:

ldy SP0X
iny
sty SP0X
jmp SCANKBD

COLLISION:

ldy SPSPCL
cpy #% 00000011 ; Did Sprite0 and Sprite1 collide?
beq APPLEGREEN ; if YES goto APPLEGREEN function
rts

APPLEGREEN:

lda #$F5
sta SP1COL    ; turn apple color green
rts

;;;;;;;;;;;;;;;  Incriment color value on keystroke C

COLORCHANGE:
ldy SP0COL
iny
sty SP0COL
jmp SCANKBD

;;;;;;;;;;;;;;;  Cause program to end on keystroke E

END:
ldy #% 00000000
sty SPENA
rts

; DUMP OUT SPRITE DATA INTO OUR AREA SP0DATA and SP1DATA

SPRITE0DATA:
.byte $03, $FF, $00, $07, $FF, $80, $0F, $FF, $80, $1F, $83, $80, $3E, $00, $80
.byte $7C, $00, $7F, $78, $00, $7E, $F8, $00, $7C, $F0, $00, $78, $F0, $00, $70 
.byte $F0, $00, $00, $F0, $00, $70, $F0, $00, $78, $F8, $00, $7C, $78, $00, $7E 
.byte $7C, $00, $7F, $3E, $00, $80, $1F, $83, $80, $0F, $FF, $80, $07, $FF, $80 
.byte $03, $FF, $00, $00                         
SPRITE1DATA:                   
.byte $02, $02, $00, $00, $88, $00, $00, $AA, $00, $0A, $AA, $80, $0A, $AA, $A0
.byte $2A, $AA, $A0, $2A, $AA, $A8, $2A, $AA, $A8, $2A, $AA, $A0, $2A, $AA, $80
.byte $2A, $AA, $80, $2A, $AA, $80, $2A, $AA, $80, $2A, $AA, $A0, $2A, $AA, $A8
.byte $2A, $AA, $A8, $0A, $AA, $A0, $0A, $AA, $A0, $02, $AA, $A0, $00, $AA, $80
.byte $00, $A2, $00, $82                                                            



NEXT----->
Understanding 6502 assembly on the Commodore 64 - (12) Recap and Beginning Registers

Table of contents

1 comment:

  1. Very nice tutorial! I'm really enjoying it so far.

    One note: In SP0INIT, you should add another ldx #$00 after returning from SPR0LOADLOOP. Right now, X is already $40 when entering SPR1LOADLOOP, so it has to run the loop 256 times so that X wraps back around to 0, then it can finally start copying our sprite data. This also has the side effect of obliterating whatever was in $2080-20bf. An even better solution, since labels are free, would be to include the *critical* "initialize X to 0" instruction *inside* the subroutine, and have an additional label for the loop.

    Example:

    SPR0LOAD:
    ldx #$00
    SPR0LOADLOOP:
    lda SPRITE0DATA,x
    sta SP0DATA,x
    inx
    cpx #$40
    bne SPR0LOADLOOP
    rts

    Looking forward to continuing the tutorial!

    ReplyDelete