; Functions for sprite handling. Mostly maintains the sprite list and provides ; utility functions to calculate sprite/tile intersections ; ; Initialize the sprite data and mask banks (all data = $0000, all masks = $FFFF) InitSprites ldx #$FFFE lda #0 :loop1 stal spritedata,x dex dex cpx #$FFFE bne :loop1 ldx #$FFFE lda #$FFFF :loop2 stal spritemask,x dex dex cpx #$FFFE bne :loop2 ; Clear values in the sprite array ; ldx #{MAX_SPRITES-1}*2 ;:loop3 stz _Sprites+TILE_STORE_ADDR_1,x ; dex ; dex ; bpl :loop3 ; Precalculate some bank values jsr _CacheSpriteBanks rts ; Utility function to calculate the difference in tile positions between a sprite's current ; position and it's previous position. This gets interesting because the number of tiles ; that a sprite covers can change based on the relative alignemen of the sprite with the ; background. ; ; Ideally, we would be able to quickly calculate exactly which new background tiles a sprite ; intersects with and which ones it has left to minimize the number of TileStore entries ; that need to be updated. ; ; In the short-term, we just do an equality test which lets us know if the sprite is ; covering the exact same tiles. ; Render a sprite stamp into the sprite buffer. Stamps exist independent of the sprites ; and sprite reference a specific stamp. This is necessary because it's common for a ; sprite to change its graphic as its animating, but it is too costly to have to set up ; the stamp every time. So this allows users to create stamps in advance and then ; assign them to the sprites as needed. ; ; Note that the user had full freedom to create a stamp at any VBUFF address, however, ; without leaving a buffer around each stamp, graphical corruption will occur. It is ; recommended that the defines for VBUFF_SPRITE_START, VBUFF_TILE_ROW_BYTES and ; VBUFF_TILE_COL_BYTES to calculate tile-aligned corner locations to lay out the ; sprite stamps in VBUFF memory. ; ; Input: ; A = sprite descriptor ; Y = vbuff address ; ; The Sprite[VBUFF_ADDR] property must be set to the vbuff address passed into this function ; to bind the sprite stamp to the sprite record. _CreateSpriteStamp pha ; Save the descriptor jsr _GetBaseTileAddr ; Get the address of the tile data tax ; Tile data address pla ; Pop the sprite ID jmp _DrawSpriteStamp ; Render the sprite data and create a stamp ; Add a new sprite to the rendering pipeline ; ; The tile id in the range 0 - 511. The top 7 bits are used as sprite control bits ; ; Bit 9 : Horizontal flip. ; Bit 10 : Vertical flip. ; Bits 11 - 12 : Sprite Size Selector ; 00 - 8x8 (1x1 tile) ; 01 - 8x16 (1x2 tiles) ; 10 - 16x8 (2x1 tiles) ; 11 - 16x16 (2x2 tiles) ; Bit 13 : Reserved. Must be zero. ; Bit 14 : Reserved. Must be zero. ; Bit 15 : Low Sprite priority. Draws behind high priority tiles. ; ; When a sprite has a size > 8x8, the horizontal tiles are taken from the next tile index and ; the vertical tiles are taken from tileId + 32. This is why tile sheets should be saved ; with a width of 256 pixels. ; ; A = tileId + flags ; Y = High Byte = x-pos, Low Byte = y-pos ; X = Sprite Slot (0 - 15) _AddSprite pha txa and #$000F asl tax pla sta _Sprites+SPRITE_ID,x ; Keep a copy of the full descriptor lda #SPRITE_STATUS_OCCUPIED+SPRITE_STATUS_ADDED sta _Sprites+SPRITE_STATUS,x stz _Sprites+VBUFF_ADDR,x ; Clear the VBUFF address, just to initialize it phy tya and #$00FF sta _Sprites+SPRITE_Y,x ; Y coordinate pla xba and #$00FF sta _Sprites+SPRITE_X,x ; X coordinate jsr _PrecalcAllSpriteInfo ; Cache sprite property values (simple stuff) ; Mark the dirty bit to indicate that the active sprite list needs to be rebuilt in the next ; render call lda #DIRTY_BIT_SPRITE_ARRAY tsb DirtyBits lda _SpriteBits,x ; Get the bit flag for this sprite slot tsb SpriteMap ; Mark it in the sprite map bit field ; txa ; And return the sprite ID ; clc ; Mark that the sprite was successfully added rts ; Alternate implementation that uses the TS_COVERAGE_SIZE and TS_LOOKUP_INDEX properties to ; load the old values directly from the TileStoreLookup table, rather than caching them. ; This is more efficient, because the work in MarkDirtySprite is independent of the ; sprite size and, by inlining the _PushDirtyTile logic, we can save a fair amount of overhead _ClearSpriteFromTileStore2 ldx _Sprites+TS_COVERAGE_SIZE,y jmp (csfts_tbl,x) csfts_tbl dw csfts_1x1,csfts_1x2,csfts_1x3,csfts_out dw csfts_2x1,csfts_2x2,csfts_2x3,csfts_out dw csfts_3x1,csfts_3x2,csfts_3x3,csfts_out dw csfts_out,csfts_out,csfts_out,csfts_out ; Just a single value to clear and add to the dirty tile list csfts_1x1 ldx _Sprites+TS_LOOKUP_INDEX,y lda TileStoreLookup,x tax lda TileStore+TS_SPRITE_FLAG,x and _SpriteBitsNot,y sta TileStore+TS_SPRITE_FLAG,x lda TileStore+TS_DIRTY,x bne csfts_1x1_out inc ; any non-zero value will work sta TileStore+TS_DIRTY,x ; and is 1 cycle faster than loading a constant value txa ldx DirtyTileCount sta DirtyTiles,x inx inx stx DirtyTileCount csfts_1x2 csfts_1x3 csfts_2x1 csfts_2x3 csfts_3x1 csfts_3x2 csfts_3x3 csfts_1x1_out rts ; This is a more interesting case where the ability to batch things up starts to produce some ; efficiency gains csfts_2x2 ldx _Sprites+TS_LOOKUP_INDEX,y ; Get the address of the old top-left corner tay ldx TileStoreLookup,y lda TileStore+TS_SPRITE_FLAG,x and _SpriteBits sta TileStore+TS_SPRITE_FLAG,x lda TileStore+TS_DIRTY,x beq *+3 phx ldx TileStoreLookup+2,y lda TileStore+TS_SPRITE_FLAG,x and _SpriteBits sta TileStore+TS_SPRITE_FLAG,x lda TileStore+TS_DIRTY,x beq *+3 phx ldx TileStoreLookup+TS_LOOKUP_SPAN,y lda TileStore+TS_SPRITE_FLAG,x and _SpriteBits sta TileStore+TS_SPRITE_FLAG,x lda TileStore+TS_DIRTY,x beq *+3 phx ldx TileStoreLookup+TS_LOOKUP_SPAN+2,y lda TileStore+TS_SPRITE_FLAG,x and _SpriteBits sta TileStore+TS_SPRITE_FLAG,x ldy DirtyTileCount lda TileStore+TS_DIRTY,x beq skip_2x2 txa sta DirtyTiles,y sta TileStore+TS_DIRTY,x skip_2x2 pla beq :done1 sta DirtyTiles+2,x tay sta TileStore+TS_DIRTY,y pla beq :done2 sta DirtyTiles+4,x tay sta TileStore+TS_DIRTY,y pla beq :done3 sta DirtyTiles+6,x tay sta TileStore+TS_DIRTY,y ; Maximum number of dirty tiles reached. Just fall through. pla txa adc #8 sta DirtyTileCount rts :done3 txa adc #6 sta DirtyTileCount rts :done2 txa adc #4 sta DirtyTileCount rts :done1 inx inx stx DirtyTileCount rts lda _SpriteBitsNot,y ; Cache the bit value for this sprite ldy TileStoreLookup,x ; Get the tile store offset and TileStore+TS_SPRITE_FLAG,y sta TileStore+TS_SPRITE_FLAG,y csfts_out rts ; Run through the list of tile store offsets that this sprite was last drawn into and mark ; those tiles as dirty. The largest number of tiles that a sprite could possibly cover is 20 ; (an unaligned 4x3 sprite), covering a 5x4 area of play field tiles. ; ; Y register = sprite record index _CSFTS_Out rts _ClearSpriteFromTileStore ; ldx _Sprites+TILE_STORE_ADDR_1,y ; beq _CSFTS_Out ; ldal TileStore+TS_SPRITE_FLAG,x ; Clear the bit in the bit field. This seems wasteful, but ; and _SpriteBitsNot,y ; there is no indexed form of TSB/TRB and caching the value in ; stal TileStore+TS_SPRITE_FLAG,x ; a direct page location, only saves 1 or 2 cycles per and costs 10. ; jsr _PushDirtyTileX ; ldx _Sprites+TILE_STORE_ADDR_2,y ; beq _CSFTS_Out ; ldal TileStore+TS_SPRITE_FLAG,x ; and _SpriteBitsNot,y ; stal TileStore+TS_SPRITE_FLAG,x ; jsr _PushDirtyTileX ; ldx _Sprites+TILE_STORE_ADDR_3,y ; beq _CSFTS_Out ; ldal TileStore+TS_SPRITE_FLAG,x ; and _SpriteBitsNot,y ; stal TileStore+TS_SPRITE_FLAG,x ; jsr _PushDirtyTileX ; ldx _Sprites+TILE_STORE_ADDR_4,y ; beq _CSFTS_Out ; ldal TileStore+TS_SPRITE_FLAG,x ; and _SpriteBitsNot,y ; stal TileStore+TS_SPRITE_FLAG,x ; jsr _PushDirtyTileX ; ldx _Sprites+TILE_STORE_ADDR_5,y ; beq :out ; ldal TileStore+TS_SPRITE_FLAG,x ; and _SpriteBitsNot,y ; stal TileStore+TS_SPRITE_FLAG,x ; jsr _PushDirtyTileX ; ldx _Sprites+TILE_STORE_ADDR_6,y ; beq :out ; ldal TileStore+TS_SPRITE_FLAG,x ; and _SpriteBitsNot,y ; stal TileStore+TS_SPRITE_FLAG,x ; jsr _PushDirtyTileX ; ldx _Sprites+TILE_STORE_ADDR_7,y ; beq :out ; ldal TileStore+TS_SPRITE_FLAG,x ; and _SpriteBitsNot,y ; stal TileStore+TS_SPRITE_FLAG,x ; jsr _PushDirtyTileX ; ldx _Sprites+TILE_STORE_ADDR_8,y ; beq :out ; ldal TileStore+TS_SPRITE_FLAG,x ; and _SpriteBitsNot,y ; stal TileStore+TS_SPRITE_FLAG,x ; jsr _PushDirtyTileX ; ldx _Sprites+TILE_STORE_ADDR_9,y ; beq :out ; ldal TileStore+TS_SPRITE_FLAG,x ; and _SpriteBitsNot,y ; stal TileStore+TS_SPRITE_FLAG,x ; jmp _PushDirtyTileX :out rts ; This function looks at the sprite list and renders the sprite plane data into the appropriate ; tiles in the code field. There are a few phases to this routine. The assumption is that ; any sprite that needs to be re-drawn has been marked as DIRTY or DAMAGED. ; ; A DIRTY sprite is one that has moved, so it needs to be erased/redrawn in the sprite ; buffer AND the tiles it covers marked for refresh. A DAMAGED sprite shared one or more ; tiles with a DIRTY sprite, so it needs to be redraw in the sprite buffer (but not erased!) ; and its tile do NOT need to be marked for refresh. ; ; In the first phase, we run through the list of dirty sprites and erase them from their ; OLD_VBUFF_ADDR. This clears the sprite plane buffers. We also iterate through the ; TILE_STORE_ADDR_X array and mark all of the tile store location that this sprite had occupied ; as dirty, as well as removing this sprite from the TS_SPRITE_FLAG bitfield. ; ; A final aspect is that any of the sprites indicated in the TS_SPRITE_FLAG are marked to be ; drawn in the next phase (since a portion of their content may have been erased if they overlap) ; ; In the second phase, the sprite is re-drawn into the sprite plane buffers and the appropriate ; Tile Store locations are marked as dirty. It is important to recognize that the sprites themselves ; can be marked dirty, and the underlying tiles in the tile store are independently marked dirty. phase1 dw :phase1_0 dw :phase1_1,:phase1_2,:phase1_3,:phase1_4 dw :phase1_5,:phase1_6,:phase1_7,:phase1_8 dw :phase1_9,:phase1_10,:phase1_11,:phase1_12 dw :phase1_13,:phase1_14,:phase1_15,:phase1_16 :phase1_16 ldy activeSpriteList+30 jsr _DoPhase1 :phase1_15 ldy activeSpriteList+28 jsr _DoPhase1 :phase1_14 ldy activeSpriteList+26 jsr _DoPhase1 :phase1_13 ldy activeSpriteList+24 jsr _DoPhase1 :phase1_12 ldy activeSpriteList+22 jsr _DoPhase1 :phase1_11 ldy activeSpriteList+20 jsr _DoPhase1 :phase1_10 ldy activeSpriteList+18 jsr _DoPhase1 :phase1_9 ldy activeSpriteList+16 jsr _DoPhase1 :phase1_8 ldy activeSpriteList+14 jsr _DoPhase1 :phase1_7 ldy activeSpriteList+12 jsr _DoPhase1 :phase1_6 ldy activeSpriteList+10 jsr _DoPhase1 :phase1_5 ldy activeSpriteList+8 jsr _DoPhase1 :phase1_4 ldy activeSpriteList+6 jsr _DoPhase1 :phase1_3 ldy activeSpriteList+4 jsr _DoPhase1 :phase1_2 ldy activeSpriteList+2 jsr _DoPhase1 :phase1_1 ldy activeSpriteList jsr _DoPhase1 :phase1_0 jmp phase1_rtn ; If this sprite has been MOVED or REMOVED, then clear its bit from the TS_SPRITE_FLAG in ; all of the tile store locations that it occupied on the previous frame and add those ; tile store locations to the dirty tile list. _DoPhase1 lda _Sprites+SPRITE_STATUS,y ora forceSpriteFlag bit #SPRITE_STATUS_MOVED+SPRITE_STATUS_REMOVED beq :no_clear jsr _ClearSpriteFromTileStore :no_clear ; Check to see if sprite was REMOVED If so, clear the sprite slot status lda _Sprites+SPRITE_STATUS,y bit #SPRITE_STATUS_REMOVED beq :out lda #SPRITE_STATUS_EMPTY ; Mark as empty (zero value) sta _Sprites+SPRITE_STATUS,y lda _SpriteBits,y ; Clear from the sprite bitmap trb SpriteMap :out rts ; Second phase takes care of drawing the sprites and marking the tiles that will need to be merged ; with pixel data from the sprite plane phase2 dw :phase2_0 dw :phase2_1,:phase2_2,:phase2_3,:phase2_4 dw :phase2_5,:phase2_6,:phase2_7,:phase2_8 dw :phase2_9,:phase2_10,:phase2_11,:phase2_12 dw :phase2_13,:phase2_14,:phase2_15,:phase2_16 :phase2_16 ldy activeSpriteList+30 jsr _DoPhase2 :phase2_15 ldy activeSpriteList+28 jsr _DoPhase2 :phase2_14 ldy activeSpriteList+26 jsr _DoPhase2 :phase2_13 ldy activeSpriteList+24 jsr _DoPhase2 :phase2_12 ldy activeSpriteList+22 jsr _DoPhase2 :phase2_11 ldy activeSpriteList+20 jsr _DoPhase2 :phase2_10 ldy activeSpriteList+18 jsr _DoPhase2 :phase2_9 ldy activeSpriteList+16 jsr _DoPhase2 :phase2_8 ldy activeSpriteList+14 jsr _DoPhase2 :phase2_7 ldy activeSpriteList+12 jsr _DoPhase2 :phase2_6 ldy activeSpriteList+10 jsr _DoPhase2 :phase2_5 ldy activeSpriteList+8 jsr _DoPhase2 :phase2_4 ldy activeSpriteList+6 jsr _DoPhase2 :phase2_3 ldy activeSpriteList+4 jsr _DoPhase2 :phase2_2 ldy activeSpriteList+2 jsr _DoPhase2 :phase2_1 ldy activeSpriteList jsr _DoPhase2 :phase2_0 jmp phase2_rtn _DoPhase2 lda _Sprites+SPRITE_STATUS,y beq :out ; If phase 1 marked us as empty, do nothing ora forceSpriteFlag and #SPRITE_STATUS_ADDED+SPRITE_STATUS_MOVED+SPRITE_STATUS_UPDATED beq :out ; Last thing to do, so go ahead and clear the flags lda #SPRITE_STATUS_OCCUPIED sta _Sprites+SPRITE_STATUS,y ; Mark the appropriate tiles as dirty and as occupied by a sprite so that the ApplyTiles ; subroutine will combine the sprite data with the tile data into the code field where it ; can be drawn to the screen. This routine is also responsible for setting the specific ; VBUFF address for each sprite's tile sheet position ; jmp _MarkDirtySprite :out rts ; Use the blttmp space to build the active sprite list. Since the sprite tiles are not drawn until later, ; it's OK to use that scratch space here. And it's just the right size, 32 bytes RebuildSpriteArray lda SpriteMap ; Get the bit field ; Unrolled loop to get the sprite index values that correspond to the set bit positions pea $FFFF ; end-of-list marker ]step equ 0 lup 4 lsr bcc :skip_1 pea ]step :skip_1 lsr bcc :skip_2 pea ]step+2 :skip_2 lsr bcc :skip_3 pea ]step+4 :skip_3 lsr bcc :skip_4 pea ]step+6 :skip_4 beq :end_1 ]step equ ]step+8 --^ :end_1 ; Now pop the values off of the stack until reaching the sentinel value. This could be unrolled, but ; it is only done once per frame. ldx #0 :loop pla bmi :out sta activeSpriteList,x inx inx bra :loop :out stx ActiveSpriteCount rts forceSpriteFlag ds 2 _RenderSprites ; Check to see if any sprites have been added or removed. If so, then we regenerate the active ; sprite list. Since adding and removing sprites is rare, this is a worthwhile tradeoff, because ; there are several places where we want to iterate over the all of the sprites, and having a list ; and not have to constantly load and test the SPRITE_STATUS just to skip unused slots can help ; streamline the code. lda #DIRTY_BIT_SPRITE_ARRAY trb DirtyBits ; clears the flag, if it was set beq :no_rebuild jsr RebuildSpriteArray :no_rebuild ; First step is to look at the StartX and StartY values. If the screen has scrolled, then it has ; the same effect as moving all of the sprites. ; ; OPTIMIZATION NOTE: Should check that the sprite actually changes position. If the screen scrolls ; by +X, but the sprite moves by -X (so it's relative position is unchanged), then ; it does NOT need to be marked as dirty. ; ; OPTIMIZATION NOTE: At this point, a decent chunk of per-tile time is spent cupdating the sprite flgas ; for a given TileStore entry. When a sprite needs to be redrawn (such as when the ; screen scrolls), the code marks every tile the sprite was on as no longer occupied ; and then marks the occupied tiles. While simple, this is very redundent when the ; screen in scrolling slowly since it is very likely that the same sprite covers the ; exact same tiles. Each pair of markings requires 35 cycles, so a basic 16x16 sprite ; could save >300 cycles per frame. With 4 or 5 sprites on screen, the saving passes ; our 1% threshold for useful optimizations. ; ; Since we cache the tile location and effective sprite coverage, we need a fast ; way to compare the old and new positions and get a list of the new tiles the sprite ; occupies and old locations that it no longer covers. It's possible that just testing ; for equality would be the easiest win to know when we can skip everything. stz forceSpriteFlag lda StartX cmp OldStartX bne :force_update lda StartY cmp OldStartY beq :no_change :force_update lda #SPRITE_STATUS_MOVED sta forceSpriteFlag :no_change ; Dispatch to the first phase of rendering the sprites. By pre-building the list, we know exactly ; how many sprite to process and they are in a contiguous array. So we on't have to keep track ; of an iterating variable ldx ActiveSpriteCount jmp (phase1,x) phase1_rtn ; Dispatch to the second phase of rendering the sprites. ldx ActiveSpriteCount jmp (phase2,x) phase2_rtn rts ; _GetTileAt ; ; Given a relative playfield coordinate [0, ScreenWidth), [0, ScreenHeight) return the ; X = horizontal point [0, ScreenTileWidth] ; Y = vertical point [0, ScreenTileHeight] ; ; Return ; C = 1, out of range ; C = 0, X = column, Y = row _GetTileAt cpx ScreenWidth bcc *+3 rts cpy ScreenHeight bcc *+3 rts tya ; carry is clear here adc StartYMod208 ; This is the code field line that is at the top of the screen cmp #208 bcc *+5 sbc #208 lsr lsr lsr tay ; This is the code field row for this point clc txa adc StartXMod164 cmp #164 bcc *+5 sbc #164 lsr lsr tax ; Could call _CopyBG0Tile with these arguments clc rts ; Small initialization routine to cache the banks for the sprite data and mask and tile/sprite stuff _CacheSpriteBanks lda #>spritemask and #$FF00 ora #^spritedata sta SpriteBanks lda #$0100 ora #^TileStore sta TileStoreBankAndBank01 lda #>tiledata and #$FF00 ora #^TileStore sta TileStoreBankAndTileDataBank lda #>TileStore and #$FF00 ora #^TileStore sta TileStoreBankDoubled rts ; A = x coordinate ; Y = y coordinate ;GetSpriteVBuffAddr ENT ; jsr _GetSpriteVBuffAddr ; rtl ; A = x coordinate ; Y = y coordinate ;_GetSpriteVBuffAddr ; pha ; tya ; clc ; adc #NUM_BUFF_LINES ; The virtual buffer has 24 lines of off-screen space ; xba ; Each virtual scan line is 256 bytes wide for overdraw space ; clc ; adc 1,s ; sta 1,s ; pla ; rts ; Version that uses temporary space (tmp15) ;_GetSpriteVBuffAddrTmp ; sta tmp15 ; tya ; clc ; adc #NUM_BUFF_LINES ; The virtual buffer has 24 lines of off-screen space ; xba ; Each virtual scan line is 256 bytes wide for overdraw space ; clc ; adc tmp15 ; rts ; Precalculate some cached values for a sprite. These are *only* to make other part of code, ; specifically the draw/erase routines more efficient. ; ; There are variations of this routine based on whether we are adding a new sprite, updating ; it's tile information, or changing its position. ; ; X = sprite index _stamp_step dw 0,12,24,36 _PrecalcAllSpriteInfo lda _Sprites+SPRITE_ID,x ; and #$3E00 xba and #$0006 tay lda _Sprites+VBUFF_ADDR,x clc adc _stamp_step,y sta _Sprites+SPRITE_DISP,x ; Set the ; Set the sprite's width and height lda #4 sta _Sprites+SPRITE_WIDTH,x lda #8 sta _Sprites+SPRITE_HEIGHT,x lda _Sprites+SPRITE_ID,x bit #$1000 ; width select beq :width_4 lda #8 sta _Sprites+SPRITE_WIDTH,x :width_4 lda _Sprites+SPRITE_ID,x bit #$0800 ; width select beq :height_8 lda #16 sta _Sprites+SPRITE_HEIGHT,x :height_8 ; Clip the sprite's bounding box to the play field size and also set a flag if the sprite ; is fully off-screen or not lda _Sprites+SPRITE_X,x bpl :pos_x lda #0 :pos_x cmp ScreenWidth bcs :offscreen ; sprite is off-screen, exit early sta _Sprites+SPRITE_CLIP_LEFT,x lda _Sprites+SPRITE_Y,x bpl :pos_y lda #0 :pos_y cmp ScreenHeight bcs :offscreen ; sprite is off-screen, exit early sta _Sprites+SPRITE_CLIP_TOP,x lda _Sprites+SPRITE_X,x clc adc _Sprites+SPRITE_WIDTH,x dec bmi :offscreen cmp ScreenWidth bcc :ok_x lda ScreenWidth dec :ok_x sta _Sprites+SPRITE_CLIP_RIGHT,x lda _Sprites+SPRITE_Y,x clc adc _Sprites+SPRITE_HEIGHT,x dec bmi :offscreen cmp ScreenHeight bcc :ok_y lda ScreenHeight dec :ok_y sta _Sprites+SPRITE_CLIP_BOTTOM,x stz _Sprites+IS_OFF_SCREEN,x ; passed all of the off-screen tests ; Calculate the clipped width and height lda _Sprites+SPRITE_CLIP_RIGHT,x sec sbc _Sprites+SPRITE_CLIP_LEFT,x inc sta _Sprites+SPRITE_CLIP_WIDTH,x lda _Sprites+SPRITE_CLIP_BOTTOM,x sec sbc _Sprites+SPRITE_CLIP_TOP,x inc sta _Sprites+SPRITE_CLIP_HEIGHT,x rts :offscreen lda #1 sta _Sprites+IS_OFF_SCREEN,x rts ; Remove a sprite from the list. Just mark its STATUS as FREE and it will be ; picked up in the next AddSprite. ; ; A = Sprite ID _RemoveSprite cmp #MAX_SPRITES bcc :ok rts :ok asl tax lda _Sprites+SPRITE_STATUS,x ora #SPRITE_STATUS_REMOVED sta _Sprites+SPRITE_STATUS,x rts ; Update the sprite's flags. We do not allow the size of a sprite to be changed. That requires ; the sprite to be removed and re-added. ; ; A = Sprite slot ; X = New Sprite Flags ; Y = New Sprite Stamp Address _UpdateSprite cmp #MAX_SPRITES bcc :ok rts :ok phx ; Save X to swap into A asl tax pla cmp _Sprites+SPRITE_ID,x ; If the flags changed, need to redraw the sprite bne :sprite_flag_change ; on the next frame tya cmp _Sprites+VBUFF_ADDR,x ; Did the stamp change? bne :sprite_stamp_change rts ; Nothing changed, so just return :sprite_flag_change sta _Sprites+SPRITE_ID,x ; Keep a copy of the full descriptor tya :sprite_stamp_change sta _Sprites+VBUFF_ADDR,x ; Just save this to stay in sync lda _Sprites+SPRITE_STATUS,x ; Mark this sprite as updated ora #SPRITE_STATUS_UPDATED sta _Sprites+SPRITE_STATUS,x jmp _PrecalcAllSpriteInfo ; Cache stuff and return ; Move a sprite to a new location. If the tile ID of the sprite needs to be changed, then ; a full remove/add cycle needs to happen ; ; A = sprite ID ; X = x position ; Y = y position _MoveSprite cmp #MAX_SPRITES bcc :ok rts :ok phx ; Save X to swap into A asl tax pla cmp _Sprites+SPRITE_X,x bne :changed1 sta _Sprites+SPRITE_X,x ; Update the X coordinate tya cmp _Sprites+SPRITE_Y,x bne :changed2 rts :changed1 sta _Sprites+SPRITE_X,x ; Update the X coordinate tya :changed2 sta _Sprites+SPRITE_Y,x ; Update the Y coordinate lda _Sprites+SPRITE_STATUS,x ora #SPRITE_STATUS_MOVED sta _Sprites+SPRITE_STATUS,x jmp _PrecalcAllSpriteInfo ; Can be specialized to only update (x,y) values