2021-10-21 13:50:07 +00:00
|
|
|
; Functions for sprie handling. Mostly maintains the sprite list and provides
|
|
|
|
; utility functions to calculate sprite/tile intersections
|
2021-08-25 14:38:02 +00:00
|
|
|
;
|
2021-10-21 13:50:07 +00:00
|
|
|
; The sprite plane actually covers two banks so that more than 32K can be used as a virtual
|
|
|
|
; screen buffer. In order to be able to draw sprites offscreen, the virtual screen must be
|
|
|
|
; wider and taller than the physical graphics screen.
|
|
|
|
;
|
|
|
|
; Initialize the sprite plane data and mask banks (all data = $0000, all masks = $FFFF)
|
2021-10-24 03:31:38 +00:00
|
|
|
InitSprites
|
2021-10-21 13:50:07 +00:00
|
|
|
ldx #$FFFE
|
|
|
|
lda #0
|
2021-10-24 03:31:38 +00:00
|
|
|
:loop1 stal spritedata,x
|
2021-10-21 13:50:07 +00:00
|
|
|
dex
|
|
|
|
dex
|
2021-10-29 02:52:48 +00:00
|
|
|
cpx #$FFFE
|
|
|
|
bne :loop1
|
2021-10-21 13:50:07 +00:00
|
|
|
|
|
|
|
ldx #$FFFE
|
|
|
|
lda #$FFFF
|
2021-10-24 03:31:38 +00:00
|
|
|
:loop2 stal spritemask,x
|
2021-10-21 13:50:07 +00:00
|
|
|
dex
|
|
|
|
dex
|
2021-10-29 02:52:48 +00:00
|
|
|
cpx #$FFFE
|
|
|
|
bne :loop2
|
2021-10-21 13:50:07 +00:00
|
|
|
|
2021-11-02 04:36:53 +00:00
|
|
|
; Clear values in the sprite array
|
|
|
|
|
|
|
|
ldx #{MAX_SPRITES-1}*2
|
|
|
|
:loop3 stz _Sprites+TILE_STORE_ADDR_1,x
|
|
|
|
dex
|
|
|
|
dex
|
|
|
|
bpl :loop3
|
|
|
|
|
2021-10-21 13:50:07 +00:00
|
|
|
rts
|
|
|
|
|
|
|
|
|
|
|
|
; This function looks at the sprite list and renders the sprite plane data into the appropriate
|
|
|
|
; tiles in the code field
|
2021-10-31 20:42:59 +00:00
|
|
|
forceSpriteFlag ds 2
|
2021-10-21 13:50:07 +00:00
|
|
|
_RenderSprites
|
2021-10-31 20:42:59 +00:00
|
|
|
|
|
|
|
; First step is to look at the StartX and StartY values. If the offsets have changed from the
|
2021-11-11 23:06:38 +00:00
|
|
|
; last time that the frame was redered, then we need to mark all of the sprites as dirty so that
|
|
|
|
; the tiles on which they were located at the previous frame will be refreshed
|
2021-10-31 20:42:59 +00:00
|
|
|
|
|
|
|
stz forceSpriteFlag
|
|
|
|
lda StartX
|
|
|
|
cmp OldStartX
|
|
|
|
beq :no_chng_x
|
|
|
|
lda #SPRITE_STATUS_DIRTY
|
|
|
|
sta forceSpriteFlag
|
|
|
|
:no_chng_x
|
|
|
|
lda StartY
|
|
|
|
cmp OldStartY
|
|
|
|
beq :no_chng_y
|
|
|
|
lda #SPRITE_STATUS_DIRTY
|
|
|
|
sta forceSpriteFlag
|
|
|
|
:no_chng_y
|
|
|
|
|
2021-11-02 04:36:53 +00:00
|
|
|
; Second step is to scan the list of sprites. A sprite is either clean or dirty. If it's dirty,
|
2021-10-31 20:42:59 +00:00
|
|
|
; then its position had changed, so we need to add tiles to the dirty queue to make sure the
|
2021-11-02 04:36:53 +00:00
|
|
|
; playfield gets update. If it's clean, we can skip everything.
|
2021-10-31 20:42:59 +00:00
|
|
|
|
2021-10-21 13:50:07 +00:00
|
|
|
ldx #0
|
2021-11-02 04:36:53 +00:00
|
|
|
:loop lda _Sprites+SPRITE_STATUS,x ; If the status is zero, that's the sentinel value
|
2021-10-21 13:50:07 +00:00
|
|
|
beq :out
|
2021-10-31 20:42:59 +00:00
|
|
|
ora forceSpriteFlag
|
2021-11-11 23:06:38 +00:00
|
|
|
ora #SPRITE_STATUS_DIRTY ; If the dirty flag is set, do the things....
|
2021-10-31 20:42:59 +00:00
|
|
|
bne :render
|
2021-10-21 13:50:07 +00:00
|
|
|
:next inx
|
|
|
|
inx
|
|
|
|
bra :loop
|
|
|
|
:out rts
|
|
|
|
|
2021-10-27 05:14:19 +00:00
|
|
|
; This is the complicated part; we need to draw the sprite into the sprite plane, but then
|
2021-11-11 23:06:38 +00:00
|
|
|
; calculate the tiles that overlap with the sprite potentially and mark those as dirty _AND_
|
|
|
|
; store the appropriate sprite plane address from which those tiles need to copy.
|
2021-10-24 03:31:38 +00:00
|
|
|
:render
|
2021-10-29 02:41:01 +00:00
|
|
|
stx tmp0 ; stash the X register
|
2021-10-31 20:42:59 +00:00
|
|
|
txy ; switch to the Y register
|
|
|
|
|
2021-11-02 04:36:53 +00:00
|
|
|
; Run through the list of tile store offsets that this sprite was last drawn into and mark
|
2021-11-11 23:06:38 +00:00
|
|
|
; those tiles as dirty. The largest number tiles that a sprite could possibly cover is 20
|
|
|
|
; (an unaligned 4x3 sprite), covering a 5x4 area of play field tiles.
|
2021-11-02 04:36:53 +00:00
|
|
|
;
|
2021-11-11 23:06:38 +00:00
|
|
|
; For now, we limit ourselves to 4 tiles until things are working....
|
2021-11-02 04:36:53 +00:00
|
|
|
|
|
|
|
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
|
2021-11-11 23:06:38 +00:00
|
|
|
lda _Sprites+TILE_STORE_ADDR_5,y
|
|
|
|
beq :erase_done
|
|
|
|
jsr _PushDirtyTile
|
|
|
|
lda _Sprites+TILE_STORE_ADDR_6,y
|
|
|
|
beq :erase_done
|
|
|
|
jsr _PushDirtyTile
|
|
|
|
lda _Sprites+TILE_STORE_ADDR_7,y
|
|
|
|
beq :erase_done
|
|
|
|
jsr _PushDirtyTile
|
|
|
|
lda _Sprites+TILE_STORE_ADDR_8,y
|
|
|
|
beq :erase_done
|
|
|
|
jsr _PushDirtyTile
|
|
|
|
lda _Sprites+TILE_STORE_ADDR_9,y
|
|
|
|
beq :erase_done
|
|
|
|
jsr _PushDirtyTile
|
2021-11-02 04:36:53 +00:00
|
|
|
:erase_done
|
|
|
|
|
2021-11-11 23:06:38 +00:00
|
|
|
; Really, we should only be erasing and redrawing a sprite if its local coordinates change. Look into this
|
2021-11-02 04:36:53 +00:00
|
|
|
; 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
|
2021-11-11 23:06:38 +00:00
|
|
|
; jsr _EraseTileSprite ; erase from the old position
|
2021-10-29 03:18:33 +00:00
|
|
|
|
2021-10-31 20:42:59 +00:00
|
|
|
; Draw the sprite into the sprint plane buffer(s)
|
2021-10-29 03:18:33 +00:00
|
|
|
|
2021-10-31 20:42:59 +00:00
|
|
|
ldx _Sprites+VBUFF_ADDR,y ; Get the address in the sprite plane to draw at
|
|
|
|
lda _Sprites+TILE_DATA_OFFSET,y ; and the tile address of the tile
|
2021-10-29 03:18:33 +00:00
|
|
|
tay
|
2021-10-21 13:50:07 +00:00
|
|
|
jsr _DrawTileSprite ; draw the sprite into the sprite plane
|
2021-11-11 23:06:38 +00:00
|
|
|
tya
|
|
|
|
clc
|
|
|
|
adc #128
|
|
|
|
tay
|
|
|
|
txa
|
|
|
|
clc
|
|
|
|
adc #4
|
|
|
|
tax
|
|
|
|
jsr _DrawTileSprite
|
|
|
|
|
|
|
|
tya
|
|
|
|
clc
|
|
|
|
adc #128*31
|
|
|
|
tay
|
|
|
|
txa
|
|
|
|
clc
|
|
|
|
adc #{8*256}-4
|
|
|
|
tax
|
|
|
|
jsr _DrawTileSprite
|
|
|
|
tya
|
|
|
|
clc
|
|
|
|
adc #128
|
|
|
|
tay
|
|
|
|
txa
|
|
|
|
clc
|
|
|
|
adc #4
|
|
|
|
tax
|
|
|
|
jsr _DrawTileSprite
|
2021-10-21 13:50:07 +00:00
|
|
|
|
2021-10-31 20:42:59 +00:00
|
|
|
; Mark the appropriate tiles as dirty and as occupied by a sprite so that the ApplyTiles
|
|
|
|
; subroutine will get the drawn data from the sprite plane into the code field where it
|
|
|
|
; can be drawn to the screen
|
2021-10-21 13:50:07 +00:00
|
|
|
|
2021-10-31 20:42:59 +00:00
|
|
|
ldx tmp0 ; Restore the index into the sprite array
|
2021-11-11 23:06:38 +00:00
|
|
|
jsr _MarkDirtySprite ; Mark the tiles that this sprite overlaps as dirty
|
2021-11-02 04:36:53 +00:00
|
|
|
|
|
|
|
ldx tmp0 ; Restore the index again
|
2021-11-11 23:06:38 +00:00
|
|
|
brl :next
|
2021-10-29 02:41:01 +00:00
|
|
|
|
2021-11-11 23:06:38 +00:00
|
|
|
; Marks an 8x8 square as dirty. The work here is mapping from local screen coordinates to the
|
2021-10-31 20:42:59 +00:00
|
|
|
; tile store indices. The first step is to adjust the sprite coordinates based on the current
|
|
|
|
; code field offsets and then cache variations of this value needed in the rest of the subroutine
|
|
|
|
;
|
2021-11-11 23:06:38 +00:00
|
|
|
; The SpriteX is always the MAXIMUM value of the corner coordinates. We subtract (SpriteX + StartX) mod 4
|
|
|
|
; to find the coordinate in the sprite plane that matches up with the tile in the play field and
|
|
|
|
; then use that to calculate the VBUFF address from which to copy sprite data.
|
2021-10-31 20:42:59 +00:00
|
|
|
;
|
2021-11-11 23:06:38 +00:00
|
|
|
; StartX SpriteX z = * mod 4 (SpriteX - z)
|
2021-10-31 20:42:59 +00:00
|
|
|
; ----------------------------------------------
|
|
|
|
; 0 8 0 8
|
|
|
|
; 1 8 1 7
|
|
|
|
; 2 8 2 6
|
|
|
|
; 3 8 3 5
|
|
|
|
; 4 9 1 8
|
|
|
|
; 5 9 2 7
|
|
|
|
; 6 9 3 6
|
|
|
|
; 7 9 0 9
|
|
|
|
; 8 10 2 8
|
|
|
|
; ...
|
|
|
|
;
|
|
|
|
; For the Y-coordinate, we just use "mod 8" instead of "mod 4"
|
2021-11-02 04:36:53 +00:00
|
|
|
;
|
|
|
|
; On input, X register = Sprite Array Index
|
2021-10-31 20:42:59 +00:00
|
|
|
_MarkDirtySprite8x8
|
2021-10-21 13:50:07 +00:00
|
|
|
|
2021-11-11 23:06:38 +00:00
|
|
|
stz _Sprites+TILE_STORE_ADDR_1,x ; Clear the Dirty Tile list in case of an early exit
|
2021-11-02 04:36:53 +00:00
|
|
|
|
2021-10-31 20:42:59 +00:00
|
|
|
; First, bounds check the X and Y coodinates of the sprite and, if they pass, pre-calculate some
|
2021-11-11 23:06:38 +00:00
|
|
|
; values that we can use later.
|
2021-10-21 13:50:07 +00:00
|
|
|
|
2021-10-31 20:42:59 +00:00
|
|
|
lda _Sprites+SPRITE_Y,x ; This is a signed value
|
|
|
|
bpl :y_is_pos
|
|
|
|
cmp #$FFF9 ; If a tile is <= -8 do nothing, it's off-screen
|
|
|
|
bcs :y_is_ok
|
|
|
|
rts
|
|
|
|
:y_is_pos cmp ScreenHeight ; Is a tile is > ScreenHeight, it's off-screen
|
|
|
|
bcc :y_is_ok
|
|
|
|
rts
|
|
|
|
:y_is_ok
|
|
|
|
|
|
|
|
; The sprite's Y coordinate is in a range that it will impact the visible tiles that make up the play
|
2021-11-11 23:06:38 +00:00
|
|
|
; field. Figure out what tile(s) they are and what part of the sprite plane data/mask need to be
|
2021-10-31 20:42:59 +00:00
|
|
|
; accessed to overlay with the tile pixels
|
2021-10-29 02:41:01 +00:00
|
|
|
|
2021-10-21 13:50:07 +00:00
|
|
|
clc
|
2021-10-31 20:42:59 +00:00
|
|
|
adc StartYMod208 ; Adjust for the scroll offset (could be a negative number!)
|
|
|
|
tay ; Save this value
|
|
|
|
and #$0007 ; Get (StartY + SpriteY) mod 8. For negative, this is ok because 65536 mod 8 = 0.
|
|
|
|
sta tmp6
|
2021-10-21 13:50:07 +00:00
|
|
|
|
2021-10-31 20:42:59 +00:00
|
|
|
eor #$FFFF
|
|
|
|
inc
|
|
|
|
clc
|
|
|
|
adc _Sprites+SPRITE_Y,x ; subtract from the SpriteY position
|
|
|
|
sta tmp1 ; This position will line up with the tile that the sprite overlaps with
|
2021-10-21 13:50:07 +00:00
|
|
|
|
2021-10-31 20:42:59 +00:00
|
|
|
tya ; Get back the position of the sprite in the code field
|
|
|
|
bpl :ty_is_pos
|
|
|
|
clc
|
|
|
|
adc #208 ; wrap around if we are slightly off-screen
|
|
|
|
bra :ty_is_ok
|
|
|
|
:ty_is_pos cmp #208 ; check if we went too far positive
|
|
|
|
bcc :ty_is_ok
|
2021-10-21 13:50:07 +00:00
|
|
|
sbc #208
|
2021-10-31 20:42:59 +00:00
|
|
|
:ty_is_ok
|
2021-10-21 13:50:07 +00:00
|
|
|
lsr
|
|
|
|
lsr
|
2021-10-31 20:42:59 +00:00
|
|
|
lsr ; This is the row in the Tile Store for top-left corner of the sprite
|
2021-10-29 02:41:01 +00:00
|
|
|
sta tmp2
|
|
|
|
|
2021-10-31 20:42:59 +00:00
|
|
|
; Same code, except for the X coordiante
|
2021-10-29 02:41:01 +00:00
|
|
|
|
2021-10-31 20:42:59 +00:00
|
|
|
lda _Sprites+SPRITE_X,x
|
|
|
|
bpl :x_is_pos
|
|
|
|
cmp #$FFFD ; If a tile is <= -4 do nothing, it's off-screen
|
|
|
|
bcs :x_is_ok
|
|
|
|
rts
|
|
|
|
:x_is_pos cmp ScreenWidth ; Is a tile is > ScreeWidth, it's off-screen
|
|
|
|
bcc :x_is_ok
|
|
|
|
rts
|
|
|
|
:x_is_ok
|
|
|
|
clc
|
|
|
|
adc StartXMod164
|
|
|
|
tay
|
|
|
|
and #$0003
|
|
|
|
sta tmp5 ; save the mod value to test for alignment later
|
2021-10-29 02:41:01 +00:00
|
|
|
|
2021-10-31 20:42:59 +00:00
|
|
|
eor #$FFFF
|
|
|
|
inc
|
2021-10-29 02:41:01 +00:00
|
|
|
clc
|
2021-10-31 20:42:59 +00:00
|
|
|
adc _Sprites+SPRITE_X,x
|
2021-10-29 02:41:01 +00:00
|
|
|
sta tmp3
|
|
|
|
|
2021-10-31 20:42:59 +00:00
|
|
|
tya
|
|
|
|
bpl :tx_is_pos
|
|
|
|
clc
|
|
|
|
adc #164
|
|
|
|
bra :tx_is_ok
|
|
|
|
:tx_is_pos cmp #164
|
|
|
|
bcc :tx_is_ok
|
|
|
|
sbc #164
|
|
|
|
:tx_is_ok
|
|
|
|
lsr
|
|
|
|
lsr
|
|
|
|
sta tmp4
|
2021-10-29 02:41:01 +00:00
|
|
|
|
2021-11-02 04:36:53 +00:00
|
|
|
; tmp5 = X mod 4
|
|
|
|
; tmp6 = Y mod 8
|
|
|
|
;
|
2021-11-11 23:06:38 +00:00
|
|
|
; Look at these values to determine, up front, exactly which bounding tiles will need to be put into the
|
2021-11-02 04:36:53 +00:00
|
|
|
; 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
|
|
|
|
|
2021-10-31 20:42:59 +00:00
|
|
|
; 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.
|
2021-11-11 23:06:38 +00:00
|
|
|
;
|
|
|
|
; The sprite plane address calculation is x + 256 * y and there are no wrap-around considerations,
|
|
|
|
; so we can take the calculated VBUFF address and just add a single, pre-calculate constant for each
|
|
|
|
; tile
|
|
|
|
;
|
|
|
|
; The tile store addresses are more involved, because we could wrap around in the X or Y direction
|
|
|
|
; at any step, so they need to be tracked separately. However, they can be decomposed so that we
|
|
|
|
; can update each independently. If the values are pre-multiplied by 2, then calculating the
|
|
|
|
; Tile Store for X and Y is just
|
|
|
|
;
|
|
|
|
; txa
|
|
|
|
; adc TileStoreYTable,y
|
|
|
|
;
|
|
|
|
; One other consideration is that the visibility tests for the sprite coverage vs the tile store
|
|
|
|
; coverage are different. We get into the main loop is *any* part of the sprite is potentially
|
|
|
|
; visible in the play field. However, for multi-tile sprites, some of the sub-tiles that
|
|
|
|
; comprise the sprite could be totally off-screen.
|
|
|
|
;
|
|
|
|
; To handle this, we pre-filter the tile list while calculating the sprite plane and tile store
|
|
|
|
; addresses to remove any tiles that are off-screen. This provides a natural break in the subroutine
|
|
|
|
; where the actually updating values in the TileStore and _Sprites tables and marking tiles as
|
|
|
|
; dirty involves walking a single list.
|
|
|
|
;
|
|
|
|
; A final note. Although this seems like a lot of code, rendering each tile requires, at a minimum,
|
|
|
|
; 16 LDA/STA pairs plus the overhead of the dirty tile list (~50 cycles), and possible much more.
|
|
|
|
; It's safe to assume that each tile no drawn saves around 500 cycles per frame.
|
|
|
|
;
|
|
|
|
; The worst-case for sprites is 16 sprites, all the maximum size of 4x3 and all unaligned, so
|
|
|
|
; 16 * 5 * 4 = 320 tiles total. There are, at most, 1066 tiles visible on a full-screen. This
|
|
|
|
; would effectively halve the framerate.
|
2021-11-02 04:36:53 +00:00
|
|
|
:mark1x1
|
|
|
|
sta _Sprites+TILE_STORE_ADDR_2,y ; Terminate the list after one item
|
2021-10-29 02:41:01 +00:00
|
|
|
|
2021-11-02 04:36:53 +00:00
|
|
|
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
|
2021-11-11 23:06:38 +00:00
|
|
|
; jsr :calc_col1 ; Calculate the values for the next column
|
2021-11-02 04:36:53 +00:00
|
|
|
|
|
|
|
jsr :top_left
|
|
|
|
sta _Sprites+TILE_STORE_ADDR_1,y
|
|
|
|
jsr _PushDirtyTile
|
|
|
|
|
|
|
|
jsr :top_right
|
|
|
|
sta _Sprites+TILE_STORE_ADDR_2,y
|
|
|
|
jmp _PushDirtyTile
|
|
|
|
|
|
|
|
:mark2x1
|
|
|
|
sta _Sprites+TILE_STORE_ADDR_3,y ; Terminate the list after two items
|
2021-11-11 23:06:38 +00:00
|
|
|
; jsr :calc_row1 ; Calculate the values for the next row
|
2021-11-02 04:36:53 +00:00
|
|
|
|
|
|
|
jsr :top_left
|
|
|
|
sta _Sprites+TILE_STORE_ADDR_1,y
|
|
|
|
jsr _PushDirtyTile
|
|
|
|
|
|
|
|
jsr :bottom_left
|
|
|
|
sta _Sprites+TILE_STORE_ADDR_2,y
|
|
|
|
jmp _PushDirtyTile
|
|
|
|
|
|
|
|
:mark2x2
|
2021-11-11 23:06:38 +00:00
|
|
|
sta _Sprites+TILE_STORE_ADDR_3,y ; Terminate the list after four items
|
|
|
|
|
|
|
|
; jsr :calc_col1 ; Calculate the next row and column values
|
|
|
|
; jsr :calc_row1
|
2021-11-02 04:36:53 +00:00
|
|
|
|
|
|
|
jsr :top_left
|
|
|
|
sta _Sprites+TILE_STORE_ADDR_1,y
|
2021-10-31 20:42:59 +00:00
|
|
|
jsr _PushDirtyTile
|
|
|
|
|
2021-11-02 04:36:53 +00:00
|
|
|
jsr :bottom_left
|
|
|
|
sta _Sprites+TILE_STORE_ADDR_2,y
|
|
|
|
jsr _PushDirtyTile
|
|
|
|
|
|
|
|
jsr :top_right
|
|
|
|
sta _Sprites+TILE_STORE_ADDR_3,y
|
|
|
|
jsr _PushDirtyTile
|
2021-10-31 20:42:59 +00:00
|
|
|
|
2021-11-02 04:36:53 +00:00
|
|
|
jsr :bottom_right
|
|
|
|
sta _Sprites+TILE_STORE_ADDR_4,y
|
|
|
|
jmp _PushDirtyTile
|
2021-10-29 02:41:01 +00:00
|
|
|
|
2021-11-02 04:36:53 +00:00
|
|
|
:top_left
|
|
|
|
_TileStoreOffsetX tmp4;tmp2 ; Overwrites X
|
2021-10-31 20:42:59 +00:00
|
|
|
tax
|
2021-11-02 04:36:53 +00:00
|
|
|
_SpriteVBuffAddr tmp3;tmp1 ; Does not affect X, Y
|
|
|
|
sta TileStore+TS_SPRITE_ADDR,x
|
2021-10-31 20:42:59 +00:00
|
|
|
lda #TILE_SPRITE_BIT
|
|
|
|
sta TileStore+TS_SPRITE_FLAG,x
|
|
|
|
txa
|
2021-11-02 04:36:53 +00:00
|
|
|
rts
|
2021-10-27 05:14:19 +00:00
|
|
|
|
2021-11-02 04:36:53 +00:00
|
|
|
:top_right
|
|
|
|
_TileStoreOffsetX tmp8;tmp2
|
2021-10-31 20:42:59 +00:00
|
|
|
tax
|
2021-11-02 04:36:53 +00:00
|
|
|
_SpriteVBuffAddr tmp7;tmp1
|
|
|
|
sta TileStore+TS_SPRITE_ADDR,x
|
2021-10-31 20:42:59 +00:00
|
|
|
lda #TILE_SPRITE_BIT
|
|
|
|
sta TileStore+TS_SPRITE_FLAG,x
|
2021-11-02 04:36:53 +00:00
|
|
|
txa
|
|
|
|
rts
|
|
|
|
|
|
|
|
:bottom_left
|
|
|
|
_TileStoreOffsetX tmp4;tmp10
|
|
|
|
tax
|
|
|
|
_SpriteVBuffAddr tmp3;tmp9
|
2021-10-31 20:42:59 +00:00
|
|
|
sta TileStore+TS_SPRITE_ADDR,x
|
2021-11-02 04:36:53 +00:00
|
|
|
lda #TILE_SPRITE_BIT
|
|
|
|
sta TileStore+TS_SPRITE_FLAG,x
|
2021-10-31 20:42:59 +00:00
|
|
|
txa
|
2021-11-02 04:36:53 +00:00
|
|
|
rts
|
2021-10-27 05:14:19 +00:00
|
|
|
|
2021-11-02 04:36:53 +00:00
|
|
|
: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
|
2021-10-29 02:41:01 +00:00
|
|
|
rts
|
2021-10-21 13:50:07 +00:00
|
|
|
|
|
|
|
; _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
|
|
|
|
|
|
|
|
; _DrawSprite
|
|
|
|
;
|
|
|
|
; Draw the sprites on the _Sprite list into the Sprite Plane data and mask buffers. This is using the
|
|
|
|
; tile data right now, but could be replaced with compiled sprite routines.
|
|
|
|
_DrawSprites
|
|
|
|
ldx #0
|
|
|
|
:loop lda _Sprites+SPRITE_STATUS,x
|
2021-10-29 02:41:01 +00:00
|
|
|
beq :out ; The first open slot is the end of the list
|
|
|
|
cmp #SPRITE_STATUS_DIRTY
|
|
|
|
bne :skip
|
2021-10-21 13:50:07 +00:00
|
|
|
|
2021-10-29 02:41:01 +00:00
|
|
|
phx
|
2021-10-21 13:50:07 +00:00
|
|
|
|
2021-10-29 02:41:01 +00:00
|
|
|
lda _Sprites+VBUFF_ADDR,x ; Load the address in the sprite plane
|
|
|
|
ldy _Sprites+TILE_DATA_OFFSET,x ; Load the address in the tile data bank
|
|
|
|
tax
|
2021-10-21 13:50:07 +00:00
|
|
|
jsr _DrawTileSprite
|
2021-10-29 02:41:01 +00:00
|
|
|
plx
|
|
|
|
:skip
|
|
|
|
inx
|
|
|
|
inx
|
2021-10-21 13:50:07 +00:00
|
|
|
bra :loop
|
2021-10-29 02:41:01 +00:00
|
|
|
:out rts
|
2021-10-21 13:50:07 +00:00
|
|
|
|
2021-10-29 02:41:01 +00:00
|
|
|
DrawTileSprite ENT
|
|
|
|
jsr _DrawTileSprite
|
|
|
|
rtl
|
2021-10-21 13:50:07 +00:00
|
|
|
|
2021-10-29 02:41:01 +00:00
|
|
|
_DrawTileSprite
|
2021-10-21 13:50:07 +00:00
|
|
|
phb
|
|
|
|
pea #^tiledata ; Set the bank to the tile data
|
|
|
|
plb
|
|
|
|
|
|
|
|
]line equ 0
|
|
|
|
lup 8
|
|
|
|
lda: tiledata+32+{]line*4},y
|
|
|
|
andl spritemask+{]line*256},x
|
|
|
|
stal spritemask+{]line*256},x
|
|
|
|
|
|
|
|
ldal spritedata+{]line*SPRITE_PLANE_SPAN},x
|
|
|
|
and: tiledata+32+{]line*4},y
|
2021-10-22 05:56:12 +00:00
|
|
|
ora: tiledata+{]line*4},y
|
2021-10-21 13:50:07 +00:00
|
|
|
stal spritedata+{]line*SPRITE_PLANE_SPAN},x
|
|
|
|
|
|
|
|
lda: tiledata+32+{]line*4}+2,y
|
|
|
|
andl spritemask+{]line*SPRITE_PLANE_SPAN}+2,x
|
|
|
|
stal spritemask+{]line*SPRITE_PLANE_SPAN}+2,x
|
|
|
|
|
|
|
|
ldal spritedata+{]line*SPRITE_PLANE_SPAN}+2,x
|
|
|
|
and: tiledata+32+{]line*4}+2,y
|
2021-10-22 05:56:12 +00:00
|
|
|
ora: tiledata+{]line*4}+2,y
|
2021-10-21 13:50:07 +00:00
|
|
|
stal spritedata+{]line*SPRITE_PLANE_SPAN}+2,x
|
|
|
|
]line equ ]line+1
|
|
|
|
--^
|
|
|
|
|
|
|
|
plb ; pop extra byte
|
|
|
|
plb
|
|
|
|
rts
|
|
|
|
|
|
|
|
; Erase is easy -- set an 8x8 area of the data region to all $0000 and the corresponding mask
|
|
|
|
; resgion to all $FFFF
|
|
|
|
;
|
2021-10-29 03:18:33 +00:00
|
|
|
; X = address is sprite plane -- erases an 8x8 region
|
2021-10-21 13:50:07 +00:00
|
|
|
SPRITE_PLANE_SPAN equ 256
|
|
|
|
|
2021-10-29 03:18:33 +00:00
|
|
|
EraseTileSprite ENT
|
|
|
|
jsr _EraseTileSprite
|
|
|
|
rtl
|
2021-10-21 13:50:07 +00:00
|
|
|
|
2021-10-29 03:18:33 +00:00
|
|
|
_EraseTileSprite
|
2021-10-29 02:41:01 +00:00
|
|
|
phb ; Save the bank to switch to the sprite plane
|
2021-10-21 13:50:07 +00:00
|
|
|
|
|
|
|
pea #^spritedata
|
|
|
|
plb
|
|
|
|
|
|
|
|
lda #0
|
|
|
|
sta: {0*SPRITE_PLANE_SPAN}+0,x
|
|
|
|
sta: {0*SPRITE_PLANE_SPAN}+2,x
|
|
|
|
sta: {1*SPRITE_PLANE_SPAN}+0,x
|
|
|
|
sta: {1*SPRITE_PLANE_SPAN}+2,x
|
|
|
|
sta: {2*SPRITE_PLANE_SPAN}+0,x
|
|
|
|
sta: {2*SPRITE_PLANE_SPAN}+2,x
|
|
|
|
sta: {3*SPRITE_PLANE_SPAN}+0,x
|
|
|
|
sta: {3*SPRITE_PLANE_SPAN}+2,x
|
|
|
|
sta: {4*SPRITE_PLANE_SPAN}+0,x
|
|
|
|
sta: {4*SPRITE_PLANE_SPAN}+2,x
|
|
|
|
sta: {5*SPRITE_PLANE_SPAN}+0,x
|
|
|
|
sta: {5*SPRITE_PLANE_SPAN}+2,x
|
|
|
|
sta: {6*SPRITE_PLANE_SPAN}+0,x
|
|
|
|
sta: {6*SPRITE_PLANE_SPAN}+2,x
|
|
|
|
sta: {7*SPRITE_PLANE_SPAN}+0,x
|
|
|
|
sta: {7*SPRITE_PLANE_SPAN}+2,x
|
|
|
|
|
|
|
|
pea #^spritemask
|
|
|
|
plb
|
|
|
|
|
|
|
|
lda #$FFFF
|
|
|
|
sta: {0*SPRITE_PLANE_SPAN}+0,x
|
|
|
|
sta: {0*SPRITE_PLANE_SPAN}+2,x
|
|
|
|
sta: {1*SPRITE_PLANE_SPAN}+0,x
|
|
|
|
sta: {1*SPRITE_PLANE_SPAN}+2,x
|
|
|
|
sta: {2*SPRITE_PLANE_SPAN}+0,x
|
|
|
|
sta: {2*SPRITE_PLANE_SPAN}+2,x
|
|
|
|
sta: {3*SPRITE_PLANE_SPAN}+0,x
|
|
|
|
sta: {3*SPRITE_PLANE_SPAN}+2,x
|
|
|
|
sta: {4*SPRITE_PLANE_SPAN}+0,x
|
|
|
|
sta: {4*SPRITE_PLANE_SPAN}+2,x
|
|
|
|
sta: {5*SPRITE_PLANE_SPAN}+0,x
|
|
|
|
sta: {5*SPRITE_PLANE_SPAN}+2,x
|
|
|
|
sta: {6*SPRITE_PLANE_SPAN}+0,x
|
|
|
|
sta: {6*SPRITE_PLANE_SPAN}+2,x
|
|
|
|
sta: {7*SPRITE_PLANE_SPAN}+0,x
|
|
|
|
sta: {7*SPRITE_PLANE_SPAN}+2,x
|
|
|
|
|
|
|
|
pla
|
|
|
|
plb
|
|
|
|
rts
|
|
|
|
|
|
|
|
; Add a new sprite to the rendering pipeline
|
|
|
|
;
|
2021-10-31 00:24:23 +00:00
|
|
|
; The tile id ithe range 0 - 511. The top 7 bits are used as sprite control bits
|
|
|
|
;
|
|
|
|
; Bit 9 : Horizontal flip.
|
|
|
|
; Bit 10 : Vertical flip.
|
2021-11-11 23:06:38 +00:00
|
|
|
; 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.
|
2021-10-31 00:24:23 +00:00
|
|
|
;
|
|
|
|
; 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.
|
|
|
|
;
|
|
|
|
; Single sprite are limited to 24 lines high because there are 28 lines of padding above and below the
|
|
|
|
; sprite plane buffers, so a sprite that is 32 lines high could overflow the drawing area.
|
|
|
|
;
|
|
|
|
; A = tileId + flags
|
2021-10-21 13:50:07 +00:00
|
|
|
; X = x position
|
|
|
|
; Y = y position
|
|
|
|
AddSprite ENT
|
|
|
|
phb
|
|
|
|
phk
|
|
|
|
plb
|
|
|
|
jsr _AddSprite
|
|
|
|
plb
|
|
|
|
rtl
|
|
|
|
|
|
|
|
_AddSprite
|
2021-10-24 03:31:38 +00:00
|
|
|
phx ; Save the horizontal position and tile ID
|
2021-10-21 13:50:07 +00:00
|
|
|
pha
|
|
|
|
|
|
|
|
ldx #0
|
|
|
|
:loop lda _Sprites+SPRITE_STATUS,x ; Look for an open slot
|
|
|
|
beq :open
|
|
|
|
inx
|
|
|
|
inx
|
|
|
|
cpx #MAX_SPRITES*2
|
|
|
|
bcc :loop
|
|
|
|
|
|
|
|
pla ; Early out
|
|
|
|
pla
|
2021-10-24 03:31:38 +00:00
|
|
|
sec ; Signal that no sprite slot was available
|
2021-10-21 13:50:07 +00:00
|
|
|
rts
|
|
|
|
|
|
|
|
:open lda #SPRITE_STATUS_DIRTY
|
|
|
|
sta _Sprites+SPRITE_STATUS,x ; Mark this sprite slot as occupied and that it needs to be drawn
|
|
|
|
pla
|
2021-11-11 23:06:38 +00:00
|
|
|
sta _Sprites+SPRITE_ID,x ; Keep a copy of the full descriptor
|
2021-10-31 20:42:59 +00:00
|
|
|
jsr _GetTileAddr ; This applies the TILE_ID_MASK
|
2021-10-21 13:50:07 +00:00
|
|
|
sta _Sprites+TILE_DATA_OFFSET,x
|
|
|
|
|
2021-10-31 20:42:59 +00:00
|
|
|
tya ; Y coordinate
|
|
|
|
sta _Sprites+SPRITE_Y,x
|
|
|
|
|
|
|
|
pla ; X coordinate
|
|
|
|
sta _Sprites+SPRITE_X,x
|
|
|
|
|
|
|
|
jsr _GetSpriteVBuffAddr ; Preserves X-register
|
2021-10-21 13:50:07 +00:00
|
|
|
sta _Sprites+VBUFF_ADDR,x
|
|
|
|
|
2021-10-24 03:31:38 +00:00
|
|
|
clc ; Mark that the sprite was successfully added
|
|
|
|
txa ; And return the sprite ID
|
|
|
|
rts
|
|
|
|
|
2021-10-29 02:41:01 +00:00
|
|
|
; X = x coordinate
|
|
|
|
; Y = y coordinate
|
|
|
|
GetSpriteVBuffAddr ENT
|
2021-10-31 20:42:59 +00:00
|
|
|
jsr _GetSpriteVBuffAddr
|
|
|
|
rtl
|
|
|
|
|
|
|
|
; A = x coordinate
|
|
|
|
; Y = y coordinate
|
|
|
|
_GetSpriteVBuffAddr
|
|
|
|
pha
|
2021-10-29 02:41:01 +00:00
|
|
|
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
|
2021-10-31 20:42:59 +00:00
|
|
|
sta 1,s
|
|
|
|
pla
|
|
|
|
rts
|
2021-10-29 02:41:01 +00:00
|
|
|
|
2021-10-24 03:31:38 +00:00
|
|
|
; 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
|
|
|
|
UpdateSprite ENT
|
|
|
|
phb
|
|
|
|
phk
|
|
|
|
plb
|
|
|
|
jsr _UpdateSprite
|
|
|
|
plb
|
|
|
|
rtl
|
|
|
|
|
|
|
|
_UpdateSprite
|
|
|
|
cmp #MAX_SPRITES*2 ; Make sure we're in bounds
|
|
|
|
bcc :ok
|
|
|
|
rts
|
|
|
|
|
|
|
|
:ok
|
2021-10-29 02:41:01 +00:00
|
|
|
stx tmp0 ; Save the horizontal position
|
|
|
|
and #$FFFE ; Defensive
|
2021-10-24 03:31:38 +00:00
|
|
|
tax ; Get the sprite index
|
|
|
|
|
|
|
|
lda #SPRITE_STATUS_DIRTY ; Position is changing, mark as dirty
|
|
|
|
sta _Sprites+SPRITE_STATUS,x ; Mark this sprite slot as occupied and that it needs to be drawn
|
|
|
|
|
2021-10-29 02:41:01 +00:00
|
|
|
lda _Sprites+VBUFF_ADDR,x ; Save the previous draw location for erasing
|
|
|
|
sta _Sprites+OLD_VBUFF_ADDR,x
|
|
|
|
|
2021-11-02 04:36:53 +00:00
|
|
|
; lda _Sprites+SPRITE_X,x
|
|
|
|
; sta _Sprites+OLD_SPRITE_X,x
|
|
|
|
|
|
|
|
; lda _Sprites+SPRITE_Y,x
|
|
|
|
; sta _Sprites+OLD_SPRITE_Y,x
|
|
|
|
|
2021-10-29 02:41:01 +00:00
|
|
|
lda tmp0 ; Update the X coordinate
|
|
|
|
sta _Sprites+SPRITE_X,x
|
|
|
|
|
|
|
|
tya ; Update the Y coordinate
|
|
|
|
sta _Sprites+SPRITE_Y,x
|
|
|
|
|
2021-10-31 20:42:59 +00:00
|
|
|
lda tmp0
|
|
|
|
jsr _GetSpriteVBuffAddr
|
2021-10-24 03:31:38 +00:00
|
|
|
sta _Sprites+VBUFF_ADDR,x
|
|
|
|
|
2021-10-21 13:50:07 +00:00
|
|
|
rts
|
|
|
|
|
|
|
|
; Sprite data structures. We cache quite a few pieces of information about the sprite
|
|
|
|
; to make calculations faster, so this is hidden from the caller.
|
|
|
|
;
|
|
|
|
; Each sprite record contains the following properties:
|
|
|
|
;
|
|
|
|
; +0: Sprite status word (0 = unoccupied)
|
|
|
|
; +2: Tile data address
|
|
|
|
; +4: Screen offset address (used for data and masks)
|
|
|
|
|
|
|
|
; Number of "off-screen" lines above logical (0,0)
|
|
|
|
NUM_BUFF_LINES equ 24
|
|
|
|
|
2021-11-02 04:36:53 +00:00
|
|
|
MAX_SPRITES equ 16
|
2021-11-11 23:06:38 +00:00
|
|
|
SPRITE_REC_SIZE equ 34
|
2021-10-21 13:50:07 +00:00
|
|
|
|
|
|
|
SPRITE_STATUS_EMPTY equ 0
|
|
|
|
SPRITE_STATUS_CLEAN equ 1
|
|
|
|
SPRITE_STATUS_DIRTY equ 2
|
|
|
|
|
2021-11-11 23:06:38 +00:00
|
|
|
SPRITE_STATUS equ {MAX_SPRITES*0}
|
2021-10-21 13:50:07 +00:00
|
|
|
TILE_DATA_OFFSET equ {MAX_SPRITES*2}
|
|
|
|
VBUFF_ADDR equ {MAX_SPRITES*4}
|
2021-11-11 23:06:38 +00:00
|
|
|
SPRITE_ID equ {MAX_SPRITES*6}
|
|
|
|
SPRITE_X equ {MAX_SPRITES*8}
|
|
|
|
SPRITE_Y equ {MAX_SPRITES*10}
|
|
|
|
OLD_VBUFF_ADDR equ {MAX_SPRITES*12}
|
|
|
|
TILE_STORE_ADDR_1 equ {MAX_SPRITES*14}
|
|
|
|
TILE_STORE_ADDR_2 equ {MAX_SPRITES*16}
|
|
|
|
TILE_STORE_ADDR_3 equ {MAX_SPRITES*18}
|
|
|
|
TILE_STORE_ADDR_4 equ {MAX_SPRITES*20}
|
|
|
|
TILE_STORE_ADDR_5 equ {MAX_SPRITES*22}
|
|
|
|
TILE_STORE_ADDR_6 equ {MAX_SPRITES*24}
|
|
|
|
TILE_STORE_ADDR_7 equ {MAX_SPRITES*26}
|
|
|
|
TILE_STORE_ADDR_8 equ {MAX_SPRITES*28}
|
|
|
|
TILE_STORE_ADDR_9 equ {MAX_SPRITES*30}
|
|
|
|
TILE_STORE_ADDR_10 equ {MAX_SPRITES*32}
|
2021-10-21 13:50:07 +00:00
|
|
|
|
|
|
|
_Sprites ds SPRITE_REC_SIZE*MAX_SPRITES
|