Continuing work on the pseudocode for the new Sprite Manager framework, we can now look at the logic that handles movement and cleaning-up the AST. This routine was originally three separate passes through the AST to undraw sprites, move them, and handle deactivations - but after a goodly amount of brainstorming, I managed to combine them into a single pass through the table:
; Unplot sprites by redrawing underlying character, then either zap the AST entry or calculate movement
SPZAPMVE:
FOR X = AST[0] TO AST[AST.NSAB] ; Process each sprite in the table
CALL CHARPLOT (X.SCB.7, X.SPB, X.UPCB, X.UPCN) ; Draw the original character at the screen position
IF X.SCB.6 = 0 ; Sprite active?
CALL (X.SOAL.SMAB) (X.SCB.7, X.SPB) ; Yes, call movement handler (sets X.SCB.7, X.SPB and maybe X.SCB.6)
IF X.SCB.6 = 1 ; Sprite inactive? (moved beyond end-of-path)
CALL SPKILL (X) ; Yes - destroy current sprite
END IF
ELSE
CALL SPKILL (X) ; Destroy current sprite
END IF
NEXT
We first undraw the sprite by plotting the original character that was at the screen position before the sprite moved to it during the previous cycle, and then - for any sprites that are active this time around - call the object movement handler routine, which determines what the next position will be. If the movement sends the sprite beyond any end-of-path boundary, it'll also mark it for deactivation. Lastly, any deactivated sprites get zapped from the AST using the 'SPKILL' logic outlined in the last post.
Here's an example of the movement handler for the Dropship object:
; Move the Dropship
DSMOVE:
X = AST[DROPSHIP] ; Processing the AST entry for the Dropship during SPZAPMVE
X.SPB = X.SPB + 22 ; Down 1 screen line
IF X.SPB GT 255 ; Did we cross the mid-screen page boundary?
X.SCB.7 = 1 ; Yes - set the Base Offset bit
END IF
IF X.SCB.7 = 1 AND X.SPB GT 192 ; Did we fall past the end of the playfield area?
X.SCB.6 = 1 ; Yes - set the Deactivation bit
END IF
Fairly simple, eh? Now here's the logic that happens at the end of the cycle to draw active sprites to the screen:
; Plot active sprites
SPDRAW:
FOR X = AST[0] TO AST[AST.NSAB] ; Process each sprite in the table
IF X.SCB.6 = 0 ; Sprite active?
CALL CHARPLOT (X.SCB.7, X.SPB, X.SOAL.PCB, X.SCB.0-3) ; Yep - draw the character at the screen position
END IF
NEXT
It's that easy. But here's the tricky part - this is the heart of the whole thing, the bit that makes the magic happen: testing for and handling collisions between sprites, and between sprites and the playfield (the static background stuff):
; Sprite collision-detection
SPDETECT:
FOR X = AST[0] TO AST[AST.NSAB] ; Process each sprite in the table
X.UPCB = GETUPCB (X.SCB.7, X.SPB) ; Get screen character code at sprites' position
X.UPCN = GETUPCN (X.SCB.7, X.SPB) ; Get screen colour nybble at sprites' position
FOR Y = AST[X+1] TO AST[AST.NSAB] ; Scan all subsequent sprites in the table
IF X.SCB.7 = Y.SCB.7 AND X.SPB = Y.SPB ; Does another sprite have the same target position as this one?
FOR Z = 0 TO X.SOAL.SCB.0-3 ; Yes - scan this sprites' object definition collision-objects list
IF X.SOAL.COP[Z][1] = #Y#.LO ; Is the other sprite in this sprites' collision list?
CALL (X.SOAL.COP[Z][2]) ; Yes - invoke collision-handler for these two sprite objects
BREAK ; Exit inner loop and check next sprite
END IF
NEXT
END IF
NEXT
FOR Y = SOD[PLAYFIELD] TO SOD[END] ; Process playfield SODs (at end of SOD table after sprites)
IF X.UPCB = Y.PCB ; Is this sprite going to hit a playfield character?
FOR Z = 0 TO X.SOAL.SCB.0-3 ; Yes - scan this sprites' object definition collision-objects list
IF X.SOAL.COP[Z][1] = #Y#.LO ; Is the playfield character in this sprites' collision list?
CALL (X.SOAL.COP[Z][2]) ; Yes - invoke collision-handler for the sprite and playfield objects
BREAK ; Exit inner loop and check next sprite
END IF
NEXT
END IF
NEXT
NEXT
Yep, as you'd expect this is a fairly complicated routine to look at - but actually, it's pretty simple in operation. After grabbing the screen character and colour data for where the sprite is about to go (we execute this routine after SPZAPMVE but before SPDRAW) the logic is actually just doing two things; first, a pass over all the sprite objects in the AST after this one to see if any others are going to move to the same place, and second a pass over all the playfield sprite object definitions (which go at the end of the SOD table after the 'proper' sprites) to see if any of those register as collisions. In either case, if a collision happens we call the appropriate collision-handler routine as described in the SOD for this sprite, and carry on.
If any collisions did occur which resulted in an action being taken, the collision-handler takes care of all of that, and possibly sets this or the other sprites' deactivation bit. In order for all this to hang together, things have to happen in this order:
; Update screen
UPDTSCRN:
CALL SPZAPMVE ; Undraw, move and/or zap sprites
CALL INITSPR ; See if any sprites need instantiating
CALL SPDETECT ; Check for and handle collisions
CALL SPDRAW ; Draw sprites
WAIT VSYNC ; Wait for VSYNC to draw screen
And we're done - there are a few other minor support routines and tables of addresses that sit alongside this stuff, but this is the meat on the bones. Now to turn it into real 6502 code...

0 comments:
Post a Comment