diff --git a/demos/sprites/App.Main.s b/demos/sprites/App.Main.s index 2989afd..42d79a9 100644 --- a/demos/sprites/App.Main.s +++ b/demos/sprites/App.Main.s @@ -40,16 +40,13 @@ DOWN_ARROW equ $0A ; Set up our level data jsr BG0SetUp -; jsr TileAnimInit jsr SetLimits -; Allocate room to load data -; jsr MovePlayerToOrigin ; Put the player at the beginning of the map - jsr InitOverlay ; Initialize the status bar stz frameCount ldal OneSecondCounter sta oldOneSecondCounter + jsr UdtOverlay ; Initialize the sprite's global position (this is tracked outside of the tile engine) lda #16 @@ -65,7 +62,7 @@ DOWN_ARROW equ $0A ; Add a sprite to the engine and save it's sprite ID jsr UpdatePlayerLocal - lda #3 ; 8x8 sprite, tile ID = 3 + lda #64 ; 8x8 sprite, tile ID = 64 ldx PlayerX ldy PlayerY jsl AddSprite @@ -151,6 +148,7 @@ EvtLoop cmp #$FFFA bcc :not_j :pos_xvel dec + dec sta PlayerXVel bra :do_render :not_j @@ -162,6 +160,7 @@ EvtLoop cmp #6 bcs :not_l :neg_xvel inc + inc sta PlayerXVel bra :do_render :not_l @@ -169,35 +168,14 @@ EvtLoop ; Update the camera position :do_render -; jsr UpdatePlayerPos ; Moves in global cordinates -; jsr UpdateCameraPos ; Moves the screen -; jsr UpdatePlayerLocal ; Gets local sprite coordinates + jsr UpdatePlayerPos ; Moves in global cordinates + jsr UpdateCameraPos ; Moves the screen + jsr UpdatePlayerLocal ; Gets local sprite coordinates -; lda PlayerID -; ldx PlayerX -; ldy PlayerY -; jsl UpdateSprite ; Move the sprite to this local position - -; Draw the sprite in the sprite plane - -; ldx PlayerX -; ldy PlayerY -; jsl GetSpriteVBuffAddr -; tax ; put in X -; ldy #3*128 ; draw the 3rd tile as a sprite -; stx PlayerLastPos ; save for erasure -; jsl DrawTileSprite - -; Now the sprite has been drawn. Enqueue the dirty tiles. We blindly add the potential -; dirty tiles and rely on PushDirtyTile to elimate duplicates quickly - -; ldx PlayerX -; ldy PlayerY -; jsr MakeDirtySprite8x8 - -; The dirty tile queue has been written to; apply it to the code field - -; jsl ApplyTiles + lda PlayerID + ldx PlayerX + ldy PlayerY + jsl UpdateSprite ; Move the sprite to this local position ; Let's see what it looks like! @@ -213,26 +191,6 @@ EvtLoop jsr UdtOverlay stz frameCount :noudt - -; Erase the sprites that moved - -; ldx PlayerLastPos ; Delete the sprite because it moved -; jsl EraseTileSprite - -; Add the tiles that the sprite was previously at as well. - -; ldx PlayerXOld -; ldy PlayerYOld -; jsr MakeDirtyTile8x8 - -; tax -; ldy PlayerY -; lda PlayerID -; jsl UpdateSprite - -; jsl DoTimers -; jsl Render - brl EvtLoop ; Exit code @@ -343,13 +301,13 @@ UpdatePlayerLocal lda PlayerGlobalX sec sbc StartX - jsr AdjustLocalX +; jsr AdjustLocalX sta PlayerX lda PlayerGlobalY sec sbc StartY - jsr AdjustLocalY +; jsr AdjustLocalY sta PlayerY rts @@ -411,6 +369,9 @@ UpdatePlayerPos :no_dxv sta PlayerXVel + lda PlayerStanding + bne :too_fast + lda PlayerYVel inc bmi :is_neg @@ -421,72 +382,35 @@ UpdatePlayerPos :too_fast rts -; Takes a signed playfield position (including off-screen coordinates) and a size and marks -; the tiles that are impacted by this shape. The main job of this subroutine is to ensure -; that all of the tile coordinate s are within the valid bounds [0 - 40], [0 - 25]. -; -; X = signed integer -; Y = signed integer -; A = sprite size (0 - 7) -SpriteWidths dw 4,4,8,8,12,8,12,16 -SpriteHeights dw 8,16,8,16,16,24,24,24 - ; 000 - 8x8 (1x1 tile) -; 001 - 8x16 (1x2 tiles) -; 010 - 16x8 (2x1 tiles) -; 011 - 16x16 (2x2 tiles) -; 100 - 24x16 (3x2 tiles) -; 101 - 16x24 (2x3 tiles) -; 110 - 24x24 (3x3 tiles) -; 111 - 32x24 (4x3 tiles) -MarkTilesOut - ply - plx - sec - rts - -MarkTiles - phx - phy - - and #$0007 - asl - tax - -; First, do a bound check against the whole sprite. It it's totally off-screen, do nothing because -; there are no physical tiles to mark. - - lda 1,s ; load the Y coordinate - bpl :y_pos - eor #$FFFF ; for a negative coordinate, see if it's equal to or larger than the sprite height - inc - cmp SpriteHeights,x - bcs MarkTilesOut - bra :y_ok -:y_pos cmp ScreenHeight - bcc :y_ok - bra MarkTilesOut -:y_ok - rts - - ; X = coordinate ; Y = coordinate - GetTileAt txa bmi :out + clc + adc StartXMod164 + cmp #164 + bcc *+5 + sbc #164 + lsr lsr tax tya bmi :out + clc + adc StartYMod208 + cmp #208 + bcc *+5 + sbc #208 + lsr lsr lsr tay - jsl GetTileStoreOffset + jsl GetTileStoreOffset tax ldal TileStore+TS_TILE_ID,x rts @@ -495,142 +419,6 @@ GetTileAt lda #EMPTY_TILE rts -; X = coordinate -; Y = coordinate -MakeDirtySprite8x8 - - phx - phy - - txa ; need to do a signed shift... - lsr - lsr - tax - tya - lsr - lsr - lsr - tay - jsr MakeDirtySpriteTile ; top-left - - lda 3,s - clc - adc #3 - lsr - lsr - tax - jsr MakeDirtySpriteTile ; top-right - - lda 1,s - clc - adc #7 - lsr - lsr - lsr - tay - jsr MakeDirtySpriteTile ; bottom-right - - lda 3,s - lsr - lsr - tax - jsr MakeDirtySpriteTile ; bottom-left - - ply - plx - rts - -; X = coordinate -; Y = coordinate -MakeDirtyTile8x8 - phx - phy - - txa - lsr - lsr - tax - tya - lsr - lsr - lsr - tay - jsr MakeDirtyTile ; top-left - - lda 3,s - clc - adc #3 - lsr - lsr - tax - jsr MakeDirtyTile ; top-right - - lda 1,s - clc - adc #7 - lsr - lsr - lsr - tay - jsr MakeDirtyTile ; bottom-right - - lda 3,s - lsr - lsr - tax - jsr MakeDirtyTile ; bottom-left - - ply - plx - rts - -MakeDirtyTile - phx - phy - - jsl GetTileStoreOffset - jsl PushDirtyTile - - ply - plx - rts - -MakeDirtySpriteTile - phx - phy - - txa - asl - asl - tax - tya - asl - asl - asl - tay - jsl GetSpriteVBuffAddr - - pha - - lda 3,s - tay - lda 5,s - tax - - jsl GetTileStoreOffset - tax - lda #TILE_SPRITE_BIT - stal TileStore+TS_SPRITE_FLAG,x - pla - stal TileStore+TS_SPRITE_ADDR,x - - txa - jsl PushDirtyTile - - ply - plx - rts - ; Position the screen with the botom-left corner of the tilemap visible MovePlayerToOrigin lda #0 ; Set the player's position diff --git a/macros/CORE.MACS.S b/macros/CORE.MACS.S index 02996ae..f4b3a34 100644 --- a/macros/CORE.MACS.S +++ b/macros/CORE.MACS.S @@ -139,6 +139,15 @@ _TileStoreOffset mac adc TileStoreYTable,y <<< +_TileStoreOffsetX mac + lda ]2 + asl + tax + lda ]1 + asl ; Assume in range, so asl puts a 0 bit into the carry + adc TileStoreYTable,x + <<< + _; Macro variant to calculate inline from any source _SpriteVBuffAddr mac lda ]2 diff --git a/src/Core.s b/src/Core.s index dc1d6e7..f9f1d36 100644 --- a/src/Core.s +++ b/src/Core.s @@ -8,7 +8,7 @@ use .\Defs.s ; Feature flags -NO_INTERRUPTS equ 1 ; turn off for crossrunner debugging +NO_INTERRUPTS equ 0 ; turn off for crossrunner debugging NO_MUSIC equ 1 ; turn music + tool loading off ; External data provided by the main program segment diff --git a/src/Sprite.s b/src/Sprite.s index c69f479..0298109 100644 --- a/src/Sprite.s +++ b/src/Sprite.s @@ -23,6 +23,14 @@ InitSprites 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 + rts @@ -49,12 +57,12 @@ _RenderSprites sta forceSpriteFlag :no_chng_y -; Second step is to scan the list of spries. A sprite is either clean or dirty. If it's dirty, +; Second step is to scan the list of sprites. A sprite is either clean or dirty. If it's dirty, ; then its position had changed, so we need to add tiles to the dirty queue to make sure the -; playfield gets update. If it's clean, we can skip eerything. +; playfield gets update. If it's clean, we can skip everything. ldx #0 -:loop lda _Sprites+SPRITE_STATUS,x ; If the sttus is zero, that's the sentinel value +:loop lda _Sprites+SPRITE_STATUS,x ; If the status is zero, that's the sentinel value beq :out ora forceSpriteFlag bit #SPRITE_STATUS_DIRTY ; If the dirty flag is set, do the things.... @@ -72,8 +80,32 @@ _RenderSprites stx tmp0 ; stash the X register txy ; switch to the Y register -; ldx _Sprites+OLD_VBUFF_ADDR,y -; jsr _EraseTileSprite ; erase from the old position +; Run through the list of tile store offsets that this sprite was last drawn into and mark +; those tiles as dirty. The most tiles that a sprite could possibly cover is 20 (a 4x3 sprite) +; that is offset, covering a 5x4 area of play field tiles. +; +; For now, we limit ourselved to 4 tiles until things are working.... + + lda _Sprites+TILE_STORE_ADDR_1,y + beq :erase_done + jsr _PushDirtyTile + lda _Sprites+TILE_STORE_ADDR_2,y + beq :erase_done + jsr _PushDirtyTile + lda _Sprites+TILE_STORE_ADDR_3,y + beq :erase_done + jsr _PushDirtyTile + lda _Sprites+TILE_STORE_ADDR_4,y + beq :erase_done + jsr _PushDirtyTile +:erase_done + +; Really, we should only be erasing and redrawing a sprite if its local coordinateds change. Look into this +; as a future optimization. Ideally, all of the sprites will be rendered into the sprite plane in a separate +; pass from this function, which is primarily concerned with flagging dirty tiles in the Tile Store. + + ldx _Sprites+OLD_VBUFF_ADDR,y + jsr _EraseTileSprite ; erase from the old position ; Draw the sprite into the sprint plane buffer(s) @@ -88,6 +120,8 @@ _RenderSprites ldx tmp0 ; Restore the index into the sprite array jsr _MarkDirtySprite8x8 ; Eventually will have routines for all sprite sizes + + ldx tmp0 ; Restore the index again bra :next ; Marks a 8x8 square as dirty. The work here is mapping from local screen coordinates to the @@ -112,9 +146,12 @@ _RenderSprites ; ... ; ; For the Y-coordinate, we just use "mod 8" instead of "mod 4" - +; +; On input, X register = Sprite Array Index _MarkDirtySprite8x8 + stz _Sprites+TILE_STORE_ADDR_1,x ; Clear the Dirty Tiles in case of an early exit + ; First, bounds check the X and Y coodinates of the sprite and, if they pass, pre-calculate some ; values that we can use later @@ -194,28 +231,94 @@ _MarkDirtySprite8x8 lsr sta tmp4 +; tmp5 = X mod 4 +; tmp6 = Y mod 8 +; +; Look at these values to determine, up front, exactly which tiles will need to be put into the +; dirty tile queue. +; +; tmp5 tmp6 +; ------------+ +; 0 0 | top-left only (1 tile) +; !0 0 | top row (2 tiles) +; 0 !0 | left column (2 tiles) +; !0 !0 | square (4 tiles) + + txy + + ldx #0 + lda tmp6 + beq :hop_y + ldx #4 +:hop_y + lda tmp5 + beq :hop_x + inx + inx +:hop_x + lda #0 ; shared value + jmp (:mark,x) ; pick the appropriate marking routine +:mark dw :mark1x1,:mark1x2,:mark2x1,:mark2x2 + ; At this point we have the top-left corner in the sprite plane (tmp1, tmp3) and the corresponding ; column and row in the tile store (tmp2, tmp4). The next step is to add these tile locations to ; the dirty queue and set the sprite flag along with the VBUFF location. We try to incrementally ; calculate new values to avoid re-doing work. +:mark1x1 + sta _Sprites+TILE_STORE_ADDR_2,y ; Terminate the list after one item - _SpriteVBuffAddr tmp3;tmp1 - pha - _TileStoreOffset tmp4;tmp2 - tax - lda #TILE_SPRITE_BIT - sta TileStore+TS_SPRITE_FLAG,x - pla - sta TileStore+TS_SPRITE_ADDR,x - txa + jsr :top_left + sta _Sprites+TILE_STORE_ADDR_1,y ; Returns the tile store offset + jmp _PushDirtyTile + +:mark1x2 + sta _Sprites+TILE_STORE_ADDR_3,y ; Terminate the list after two items + jsr :calc_col1 ; Calculate the values for the next column + + jsr :top_left + sta _Sprites+TILE_STORE_ADDR_1,y jsr _PushDirtyTile -; Now see if we need to extend to other tiles. If the mod values are not equal to zero, then -; the width of the sprite will extend into the adjacent code field tiles. + jsr :top_right + sta _Sprites+TILE_STORE_ADDR_2,y + jmp _PushDirtyTile - lda tmp5 - beq :no_x_oflow +:mark2x1 + sta _Sprites+TILE_STORE_ADDR_3,y ; Terminate the list after two items + jsr :calc_row1 ; Calculate the values for the next row + jsr :top_left + sta _Sprites+TILE_STORE_ADDR_1,y + jsr _PushDirtyTile + + jsr :bottom_left + sta _Sprites+TILE_STORE_ADDR_2,y + jmp _PushDirtyTile + +; This is the maximum value, so no need to terminate the list early +:mark2x2 + jsr :calc_col1 ; Calculate the next row and column values + jsr :calc_row1 + + jsr :top_left + sta _Sprites+TILE_STORE_ADDR_1,y + jsr _PushDirtyTile + + jsr :bottom_left + sta _Sprites+TILE_STORE_ADDR_2,y + jsr _PushDirtyTile + + jsr :top_right + sta _Sprites+TILE_STORE_ADDR_3,y + jsr _PushDirtyTile + + jsr :bottom_right + sta _Sprites+TILE_STORE_ADDR_4,y + jmp _PushDirtyTile + +; Functions to advance to the right, or down and cache the values in the direct page +; temporary space for re-use. col0 and row0 is the original tile +:calc_col1 lda tmp3 clc adc #4 @@ -226,60 +329,59 @@ _MarkDirtySprite8x8 bcc *+5 lda #0 sta tmp8 + rts - _SpriteVBuffAddr tmp7;tmp1 - pha - _TileStoreOffset tmp8;tmp2 - tax - lda #TILE_SPRITE_BIT - sta TileStore+TS_SPRITE_FLAG,x - pla - sta TileStore+TS_SPRITE_ADDR,x - txa - jsr _PushDirtyTile - -:no_x_oflow - lda tmp6 - beq :no_y_oflow - +:calc_row1 lda tmp1 clc adc #8 - sta tmp1 + sta tmp9 lda tmp2 inc cmp #26 bcc *+5 lda #0 - sta tmp2 + sta tmp10 + rts - _SpriteVBuffAddr tmp3;tmp1 - pha - _TileStoreOffset tmp4;tmp2 +:top_left + _TileStoreOffsetX tmp4;tmp2 ; Overwrites X tax + _SpriteVBuffAddr tmp3;tmp1 ; Does not affect X, Y + sta TileStore+TS_SPRITE_ADDR,x lda #TILE_SPRITE_BIT sta TileStore+TS_SPRITE_FLAG,x - pla - sta TileStore+TS_SPRITE_ADDR,x txa - jsr _PushDirtyTile - - lda tmp5 - beq :no_y_oflow + rts +:top_right + _TileStoreOffsetX tmp8;tmp2 + tax _SpriteVBuffAddr tmp7;tmp1 - pha - _TileStoreOffset tmp8;tmp2 - tax + sta TileStore+TS_SPRITE_ADDR,x lda #TILE_SPRITE_BIT sta TileStore+TS_SPRITE_FLAG,x - pla - sta TileStore+TS_SPRITE_ADDR,x txa - jsr _PushDirtyTile + rts -:no_y_oflow - ldx tmp0 ; Restore X register +:bottom_left + _TileStoreOffsetX tmp4;tmp10 + tax + _SpriteVBuffAddr tmp3;tmp9 + sta TileStore+TS_SPRITE_ADDR,x + lda #TILE_SPRITE_BIT + sta TileStore+TS_SPRITE_FLAG,x + txa + rts + +:bottom_right + _TileStoreOffsetX tmp8;tmp10 + tax + _SpriteVBuffAddr tmp7;tmp9 + sta TileStore+TS_SPRITE_ADDR,x + lda #TILE_SPRITE_BIT + sta TileStore+TS_SPRITE_FLAG,x + txa rts ; _GetTileAt @@ -565,6 +667,12 @@ _UpdateSprite lda _Sprites+VBUFF_ADDR,x ; Save the previous draw location for erasing sta _Sprites+OLD_VBUFF_ADDR,x +; lda _Sprites+SPRITE_X,x +; sta _Sprites+OLD_SPRITE_X,x + +; lda _Sprites+SPRITE_Y,x +; sta _Sprites+OLD_SPRITE_Y,x + lda tmp0 ; Update the X coordinate sta _Sprites+SPRITE_X,x @@ -589,8 +697,8 @@ _UpdateSprite ; Number of "off-screen" lines above logical (0,0) NUM_BUFF_LINES equ 24 -MAX_SPRITES equ 64 -SPRITE_REC_SIZE equ 12 +MAX_SPRITES equ 16 +SPRITE_REC_SIZE equ 20 SPRITE_STATUS_EMPTY equ 0 SPRITE_STATUS_CLEAN equ 1 @@ -602,5 +710,9 @@ VBUFF_ADDR equ {MAX_SPRITES*4} SPRITE_X equ {MAX_SPRITES*6} SPRITE_Y equ {MAX_SPRITES*8} OLD_VBUFF_ADDR equ {MAX_SPRITES*10} +TILE_STORE_ADDR_1 equ {MAX_SPRITES*12} +TILE_STORE_ADDR_2 equ {MAX_SPRITES*14} +TILE_STORE_ADDR_3 equ {MAX_SPRITES*16} +TILE_STORE_ADDR_4 equ {MAX_SPRITES*18} _Sprites ds SPRITE_REC_SIZE*MAX_SPRITES diff --git a/src/blitter/Tiles.s b/src/blitter/Tiles.s index 3937539..6adf57b 100644 --- a/src/blitter/Tiles.s +++ b/src/blitter/Tiles.s @@ -650,7 +650,7 @@ PushDirtyTile ENT plb rtl -_PushDirtyTile +_PushDirtyTileOld tay ; check if this already marked immediately lda TileStore+TS_DIRTY,y ; If the lookup === $FFFF (<$8000), it is free. bpl :occupied @@ -671,6 +671,25 @@ _PushDirtyTile :occupied rts +; alternate version that is very slightly slower, but preserves the y-register +_PushDirtyTile + tax + lda TileStore+TS_DIRTY,x + bpl :occupied2 + + lda DirtyTileCount + sta TileStore+TS_DIRTY,x + + pha ; Would be nice to have an "exchange a and x" instruction + txa + plx + sta DirtyTiles,x + + inx + inx + stx DirtyTileCount +:occupied2 + rts ; Remove a dirty tile from the list and return it in state ready to be rendered. It is important ; that the core rendering functions *only* use _PopDirtyTile to get a list of tiles to update, ; because this routine merges the tile IDs stored in the Tile Store with the Sprite diff --git a/src/blitter/Tiles01000.s b/src/blitter/Tiles01000.s index 608656e..77df282 100644 --- a/src/blitter/Tiles01000.s +++ b/src/blitter/Tiles01000.s @@ -5,7 +5,7 @@ ; there is no way to do everything inline, so a composite tile is created on the fly and written to ; a direct page buffer. This direct page buffer is then used to render the tile. _TBSolidSpriteTile_00 - ldx #45*128 +; ldx #45*128 jsr _TBCopyTileDataToCBuff ; Copy the tile into the compositing buffer (using correct x-register) jsr _TBApplySpriteData ; Overlay the data form the sprite plane (and copy into the code field) jmp _TBFillPEAOpcode ; Fill in the code field opcodes