Monday, 28 February 2011

Teh Pseudocodez, Part II

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