So, I switched to N++ for code-editing and managing the Assemble-Launch-Test stuff, and it all seems to be hanging together quite nicely. I fired-up the sourcecode for Meanies, and everything looked good - at which point I put my biggest thinking-cap on, and started to map out what the Sprite framework should look like. This has resulted in a good few lines of structure definitions and pseudocode, which I have now refined into an optimised form. Let's see what we've got so far.
This is the structure for a Sprite Object Definition (SOD - unfortunate nomenclature, but there it is) which largely replaces the original CHARPLOT structure:
Sprite Object Definition
------------------------
Sprite Control Byte
7 : Colour 2
6 : Colour 1
5 : Colour 0
4 : Initial Screen Position Base Offset (0=0, 1=256)
3 : Collidable Object Count 3
2 : Collidable Object Count 2
1 : Collidable Object Count 1
0 : Collidable Object Count 0
Initial Screen Position Byte
Value added to SCB.4 for initial screen position
Plot Code Byte
Character code to be plotted
Sprite Movement Address Word
1-2 : Address of movement routine for this object
Collidable Object Pointer(s)
1 : Byte reference to SOD with which this object can collide
2 : Byte reference to SCHT entry for address of collision-handler for this collision type
Notes: SOD entries must not cross page-boundaries as the lo-byte of each entry is used as the pointer in the AST.SOAL.
In this model, sprites are constrained to character-level plotting, but the design allows for this to be extended to bitmap-level (i.e. pixel-based) hi-res mode later. Each SOD occupies a minimum of 5 bytes, plus 2 more for each Collidable Object reference - if this sprite needs to do something when it hits another sprite, we add a Collidable Object reference which gives us a pointer to the other sprite(s) which can be collided-with and a pointer to the address of the routine to call when we collide with it. The SCHT (Sprite Collision Handler Table) is a simple table of addresses which point to the collision-handlers:
Sprite Collision Handler Table
------------------------------
Sprite Collision Address Word
1-2 : Address of collision-handler between first/second objects
Notes: SCHT entries must not cross page-boundaries as the lo-byte of each entry is used as the pointer in the SOD.COP.
So these two tables together represent the 'static' object definitions and the pointers to the routines which handle their movement and any collisions with other objects. If a SOD can't collide with anything (that is, if we don't want to know when it collides) then it has no COP entries; on the other hand, anything we want to know about collisions with, and do something about, has a two-byte COP entry which points to the other SOD entry we want to register collisions with, and to the address of the routine which handles those collisions. Each SOD can register up to 15 collidable objects, but that's only limited due to the use of 4 bits in the SCB.
So we now need a structure which manages 'active' sprites - as we instantiate a sprite based on its' SOD entry, we have to keep track of where it is and what it's doing, and that's where the Active Sprite Table makes its' appearance:
Active Sprite Table
-------------------
Next Slot Address Byte [Next free AST slot]
Last Slot Address Byte [Last AST slot at end of table]
Sprite Object Address Lo-Byte [Initialised from SOD Address]
Sprite Control Byte [Initialised from SOD.SCB]
7 : Screen Position Base Offset (0=0, 1=256)
6 : Deactivate Sprite (0=Do not deactivate, 1=Deactivate)
5 : Unused
4 : Unused
3 : Unused
2 : Colour 2
1 : Colour 1
0 : Colour 0
Screen Position Byte [Initialised from SOD.ISPB]
Undraw Plot Code Byte [Initialised to screen character code]
Undraw Plot Colour Nybble [Initialised to screen character colour]
7 : Unused
6 : Unused
5 : Unused
4 : Unused
3 : Colour 3
2 : Colour 2
1 : Colour 1
0 : Colour 0
Notes: Sprites are active when they have an entry in the AST - when inactive, they do not appear here.
AST entries must not cross page-boundaries since the NSAB and LSAB are indexes into the table.
The first two bytes of the AST control where the current end-of-table point is and what the maximum is - each AST object entry is 5 bytes, which means that if we align the table to the start of a page it can host up to 50 sprites simultaneously. Meanies is going to need about a dozen, so we've got the option to reduce the maximum table size and have it share page space with the SOD table - ideally these will both sit in the Zero Page, meaning all the accesses can be written using ZP addressing for speed. For projects needing more sprites, the tables might have to sit in separate pages, and I'd need to tweak the code to do 'normal' address-mode access.
Speaking of code, let's look at the pseudocode for instantiating a sprite from the SOD into the AST:
; Sprite activation (called as new sprites are needed)
SPCREATE:
IF AST.NSAB GT AST.LSAB ; Is next free slot address higher than last available slot address?
ERROR ; Yep - too many sprites, crash and burn
ELSE
X = AST[AST.NSAB] ; No - there is a free AST slot
Y = SOD ; Specified sprite object definition for this new active sprite
X.SOAL = #Y#.LO ; Populate new AST entry, lo-byte of address of the SOD
X.SCB = Y.SCB ; Control Byte
X.SPB = Y.ISPB ; Set Screen Position to Initial Position
X.SCB.0 = X.SCB.5 ; Move sprite colour from bits 5-7 to bits 0-2 (LSR)
X.SCB.1 = X.SCB.6
X.SCB.2 = X.SCB.7
X.SCB.7 = X.SCB.4 ; Move Initial Screen Base bit 4 to Base bit 7 (ROR)
AST.NSAB = AST.NSAB + 5 ; Increment pointer to next free AST slot
END IF
Assuming there's a free slot at the end of the AST, we copy the SOD pointer, its' Control Byte, and Initial Screen Position Byte to the new AST entry, shuffle the bits in the SCB around to throw away the COP count and put things in positions which are faster to work with, and increment the AST pointer to the next slot. The two other AST bytes (UPCB and UPCN) don't need to be initialised - they're populated later, as we'll see. And that's it, the sprite is now active!
I'll end this post with the reciprocal code to deactivate a sprite and destroy its' AST entry. This routine gets called whenever a sprite is registered as being out of play, which arises either when a collision event occurs which kills it, or if it reaches an end-of-path situation (such as falling off the screen):
; Destroy a sprite AST entry and shuffle the rest down
SPKILL:
FOR X = AST[N] TO AST[AST.NSAB] ; Process sprites from 'N' to the end of the AST
X.SOAL = X+1.SOAL ; Copy all sprite data down a slot (destroys object N)
X.SCB = X+1.SCB
X.SPB = X+1.SPB
X.UPCB = X+1.UPCB
X.UPCN = X+1.UPCN
NEXT
AST.NSAB = AST.NSAB - 5 ; Decrement pointer to next free AST slot
That's it for now - next time, I'll present the pseudocode for drawing/undrawing sprites, and the special sauce that is the collision-detection routine. I'll also demonstrate the order in which these various routines must be called during each frame-display cycle, and maybe post some actual code. Stay tuned!

0 comments:
Post a Comment