mirror of
https://github.com/lscharen/iigs-game-engine.git
synced 2025-02-27 06:29:30 +00:00
Small improvements to sprite prototype to fix dirty tiles getting out of sync
This commit is contained in:
parent
33280dc5c5
commit
2f73b9acf5
@ -45,11 +45,16 @@ DOWN_ARROW equ $0A
|
||||
; 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
|
||||
|
||||
; Add a player sprite
|
||||
lda #80
|
||||
lda #0
|
||||
sta PlayerX
|
||||
sta PlayerXOld
|
||||
lda #100
|
||||
lda #14
|
||||
sta PlayerY
|
||||
sta PlayerYOld
|
||||
lda #1
|
||||
@ -68,25 +73,41 @@ EvtLoop
|
||||
and #$007F ; Ignore the buttons for now
|
||||
|
||||
cmp #'q'
|
||||
bne :1
|
||||
bne :not_q
|
||||
brl Exit
|
||||
|
||||
:1
|
||||
:not_q
|
||||
cmp #'x'
|
||||
bne :not_x
|
||||
lda #$0001
|
||||
jsr UpdatePlayerPos
|
||||
bra :4
|
||||
:not_x
|
||||
|
||||
cmp #'y'
|
||||
bne :not_y
|
||||
lda #$0002
|
||||
jsr UpdatePlayerPos
|
||||
bra :4
|
||||
:not_y
|
||||
|
||||
cmp #'r'
|
||||
beq :3
|
||||
|
||||
cmp #'n'
|
||||
beq :2
|
||||
stz KeyState
|
||||
bra EvtLoop
|
||||
bra :4
|
||||
:2
|
||||
lda KeyState ; Wait for key up / key down
|
||||
bne EvtLoop
|
||||
bne :4
|
||||
lda #1
|
||||
sta KeyState
|
||||
:3
|
||||
lda #$0003
|
||||
jsr UpdatePlayerPos
|
||||
|
||||
:4
|
||||
; Draw the sprite in the sprite plane
|
||||
|
||||
ldx PlayerX
|
||||
@ -104,12 +125,6 @@ EvtLoop
|
||||
ldy PlayerY
|
||||
jsr MakeDirtySprite8x8
|
||||
|
||||
; Add the tiles that the sprite was previously at as well.
|
||||
|
||||
ldx PlayerXOld
|
||||
ldy PlayerYOld
|
||||
jsr MakeDirtyTile8x8
|
||||
|
||||
; The dirty tile queue has been written to; apply it to the code field
|
||||
|
||||
jsl ApplyTiles
|
||||
@ -118,6 +133,19 @@ EvtLoop
|
||||
|
||||
jsl Render
|
||||
|
||||
; Update the performance counters
|
||||
|
||||
inc frameCount
|
||||
ldal OneSecondCounter
|
||||
cmp oldOneSecondCounter
|
||||
beq :noudt
|
||||
sta oldOneSecondCounter
|
||||
jsr UdtOverlay
|
||||
stz frameCount
|
||||
:noudt
|
||||
|
||||
; Erase the sprites that moved
|
||||
|
||||
ldx PlayerLastPos ; Delete the sprite because it moved
|
||||
jsl EraseTileSprite
|
||||
|
||||
@ -125,6 +153,14 @@ EvtLoop
|
||||
ldy PlayerYOld ; at the old position.
|
||||
jsr ClearSpriteFlag8x8
|
||||
|
||||
; Add the tiles that the sprite was previously at as well.
|
||||
|
||||
ldx PlayerXOld
|
||||
ldy PlayerYOld
|
||||
jsr MakeDirtyTile8x8
|
||||
|
||||
|
||||
|
||||
; tax
|
||||
; ldy PlayerY
|
||||
; lda PlayerID
|
||||
@ -156,46 +192,130 @@ PlayerXVel ds 2
|
||||
PlayerYVel ds 2
|
||||
KeyState ds 2
|
||||
|
||||
UpdatePlayerPos
|
||||
oldOneSecondCounter ds 2
|
||||
frameCount ds 2
|
||||
|
||||
PLAYER_X_MIN equ 65536-3
|
||||
PLAYER_X_MAX equ 159
|
||||
PLAYER_Y_MIN equ 65536-7
|
||||
PLAYER_Y_MAX equ 199
|
||||
|
||||
; Need to use signed comparisons here
|
||||
; @see http://6502.org/tutorials/compare_beyond.html
|
||||
UpdatePlayerPosX
|
||||
lda PlayerX ; Move the player sprite a bit
|
||||
sta PlayerXOld
|
||||
clc
|
||||
adc PlayerXVel
|
||||
sta PlayerX
|
||||
|
||||
cmp #160-4
|
||||
bcc :x_ok_1
|
||||
lda #$FFFF
|
||||
sta PlayerXVel
|
||||
:x_ok_1 cmp #0
|
||||
bne :x_ok_2
|
||||
lda #$0001
|
||||
sta PlayerXVel
|
||||
:x_ok_2
|
||||
; Compate PlayerX with the X_MIN value. BMI if PlayerX < X_MIN, BPL is PlayerX >= X_MIN
|
||||
|
||||
lda PlayerY
|
||||
cmp #PLAYER_X_MIN
|
||||
beq :x_flip
|
||||
|
||||
cmp #PLAYER_X_MAX
|
||||
bne :x_ok
|
||||
:x_flip
|
||||
lda PlayerXVel
|
||||
eor #$FFFF
|
||||
inc
|
||||
sta PlayerXVel
|
||||
:x_ok
|
||||
rts
|
||||
|
||||
UpdatePlayerPosY
|
||||
lda PlayerY
|
||||
sta PlayerYOld
|
||||
clc
|
||||
adc PlayerYVel
|
||||
sta PlayerY
|
||||
|
||||
cmp #200-8
|
||||
bcc :y_ok_1
|
||||
lda #$FFFF
|
||||
cmp #PLAYER_Y_MIN
|
||||
beq :y_flip
|
||||
|
||||
cmp #PLAYER_Y_MAX
|
||||
bne :y_ok
|
||||
:y_flip
|
||||
lda PlayerYVel
|
||||
eor #$FFFF
|
||||
inc
|
||||
sta PlayerYVel
|
||||
:y_ok_1 cmp #0
|
||||
bne :y_ok_2
|
||||
lda #$0001
|
||||
sta PlayerYVel
|
||||
:y_ok_2
|
||||
:y_ok
|
||||
rts
|
||||
|
||||
UpdatePlayerPos
|
||||
pha
|
||||
bit #$0001
|
||||
beq :skip_x
|
||||
jsr UpdatePlayerPosX
|
||||
|
||||
:skip_x pla
|
||||
bit #$0002
|
||||
beq :skip_y
|
||||
jsr UpdatePlayerPosY
|
||||
|
||||
:skip_y
|
||||
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
|
||||
MakeDirtySprite8x8
|
||||
|
||||
phx
|
||||
phy
|
||||
|
||||
txa
|
||||
txa ; need to do a signed shift...
|
||||
lsr
|
||||
lsr
|
||||
tax
|
||||
@ -397,10 +517,8 @@ MovePlayerToOrigin
|
||||
qtRec adrl $0000
|
||||
da $00
|
||||
|
||||
PUT ../shell/Overlay.s
|
||||
PUT gen/App.TileMapBG0.s
|
||||
PUT gen/App.TileSetAnim.s
|
||||
|
||||
Overlay ENT
|
||||
rtl
|
||||
|
||||
ANGLEBNK ENT
|
@ -119,6 +119,16 @@ min mac
|
||||
lda ]1
|
||||
mout <<<
|
||||
|
||||
asr16 mac
|
||||
cmp #$8000
|
||||
ror
|
||||
<<<
|
||||
|
||||
asr8 mac
|
||||
cmp #$80
|
||||
ror
|
||||
<<<
|
||||
|
||||
; Macro to define script steps
|
||||
ScriptStep MAC
|
||||
IF #=]5
|
||||
@ -128,6 +138,76 @@ ScriptStep MAC
|
||||
FIN
|
||||
<<<
|
||||
|
||||
; A specialized CopyMaskedWord macro that draws a tile from a direct page workspace. Used
|
||||
; to render fringe tiles and sprite tiles when BG1 is active. If there is no second background,
|
||||
; then one should use the optimized functions which assumes a PEA opcode and only
|
||||
; needs to copy data words
|
||||
;
|
||||
; ]1 : tiledata direct page address , the tilemask direct page address is tiledata + 32
|
||||
; ]2 : code field offset
|
||||
CopyMaskedWordD MAC
|
||||
lda ]1+32 ; load the mask value
|
||||
bne mixed ; a non-zero value may be mixed
|
||||
|
||||
; This is a solid word
|
||||
lda #$00F4 ; PEA instruction
|
||||
sta: ]2,y
|
||||
ldal ]1 ; load the tile data
|
||||
sta: ]2+1,y ; PEA operand
|
||||
bra next
|
||||
|
||||
mixed cmp #$FFFF ; All 1's in the mask is fully transparent
|
||||
beq transparent
|
||||
|
||||
; This is the slowest path because there is a *lot* of work to do. So much that it's
|
||||
; worth it to change up the environment to optimize things a bit more.
|
||||
;
|
||||
; Need to fill in the first 8 bytes of the JMP handler with the following code sequence
|
||||
;
|
||||
; lda (00),y
|
||||
; and #MASK
|
||||
; ora #DATA
|
||||
|
||||
lda #$004C ; JMP instruction
|
||||
sta: ]2,y
|
||||
|
||||
ldx _X_REG ; Get the addressing offset
|
||||
ldal JTableOffset,x ; Get the address offset and add to the base address
|
||||
adc _BASE_ADDR ; of the current code field line
|
||||
adc #{]2&$F000} ; adjust for the current row offset
|
||||
sta: ]2+1,y
|
||||
|
||||
tay ; This becomes the new address that we use to patch in
|
||||
txa ; Get the offset and render a LDA (dp),y instruction
|
||||
|
||||
sep #$20
|
||||
sta: $0001,y ; LDA (00),y operand
|
||||
lda #$B1
|
||||
sta: $0000,y ; LDA (00),y opcode
|
||||
lda #$29
|
||||
sta: $0002,y ; AND #$0000 opcode
|
||||
lda #$09
|
||||
sta: $0005,y ; ORA #$0000 opcode
|
||||
rep #$20
|
||||
|
||||
lda ]1+32 ; insert the tile mask and data into the exception
|
||||
sta: $0003,y ; handler.
|
||||
lda ]1
|
||||
sta: $0006,y
|
||||
|
||||
ldy _Y_REG ; restore original y-register value and move on
|
||||
bra next
|
||||
|
||||
; This is a transparent word, so just show the second background layer
|
||||
transparent
|
||||
lda #$00B1 ; LDA (dp),y instruction
|
||||
sta: ]2,y
|
||||
lda _X_REG ; X is the logical tile offset (0, 2, 4, ... 82) left-to-right
|
||||
ora #$4800 ; put a PHA after the offset
|
||||
sta: ]2+1,y
|
||||
next
|
||||
eom
|
||||
|
||||
; Macros to use in the Masked Tile renderer
|
||||
;
|
||||
; ]1 : tiledata offset
|
||||
@ -243,3 +323,56 @@ CopyMaskedDWord MAC
|
||||
lda #$0290 ; BCC *+4
|
||||
sta: $0006,x
|
||||
eom
|
||||
|
||||
|
||||
; Masked renderer for a dynamic tile with sprite data overlaid. What's interesting about this renderer is that the mask
|
||||
; value is not used directly, but simply indicates if we can use a LDA 0,x / PHA sequence,
|
||||
; a LDA (00),y / PHA, or a JMP to a blended render
|
||||
;
|
||||
; If a dynamic tile is animated, there is the possibility to create a special mask that marks
|
||||
; words of the tile that a front / back / mixed across all frames.
|
||||
;
|
||||
; ]1 : tiledata offset
|
||||
; ]2 : tilemask offset
|
||||
; ]3 : code field offset
|
||||
CopyMaskedDynSpriteWord MAC
|
||||
|
||||
; Need to fill in the first 12(!!) bytes of the JMP handler with the following code sequence
|
||||
;
|
||||
; lda (00),y
|
||||
; and $80,x
|
||||
; ora $00,x
|
||||
; and #MASK
|
||||
; ora #DATA
|
||||
;
|
||||
; If MASK == 0, then we can do a PEA. If MASK == $FFFF, then fall back to the simple Dynamic Masked
|
||||
; code.
|
||||
|
||||
|
||||
ldx _X_REG ; Get the addressing offset
|
||||
ldal JTableOffset,x ; Get the address offset and add to the base address
|
||||
adc _BASE_ADDR ; of the current code field line
|
||||
adc #{]1&$F000} ; adjust for the current row offset
|
||||
sta: ]1+1,y
|
||||
|
||||
tax ; This becomes the new address that we use to patch in
|
||||
lda _X_REG ; Get the offset and render a LDA (dp),y instruction
|
||||
|
||||
sep #$20 ; Easier to do 8-bit operations
|
||||
sta: $0001,x ; Set the LDA (00),y operand
|
||||
lda #$B1
|
||||
sta: $0000,x ; Set the LDA (00),y opcode
|
||||
|
||||
lda _T_PTR
|
||||
sta: $0005,x ; Set ORA 00,x operand
|
||||
ora #$80
|
||||
sta: $0003,x ; Set AND 00,x operand
|
||||
lda #$35
|
||||
sta: $0002,x ; Set AND 00,x operand
|
||||
lda #$15
|
||||
sta: $0004,x ; Set ORA 00,x operand
|
||||
rep #$30
|
||||
|
||||
lda #$0290 ; BCC *+4
|
||||
sta: $0006,x
|
||||
eom
|
||||
|
@ -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
|
||||
|
130
src/Render.s
130
src/Render.s
@ -92,17 +92,13 @@ _Render
|
||||
|
||||
jsr _ShadowOff
|
||||
|
||||
; Shadowing is turned off. Render all of the scan lines that need a second pass. These
|
||||
; are the lines that have a masked overlay, or a sprite. One optimization that can
|
||||
; be done here is that the lines can be rendered in any order since it is not shown
|
||||
; on-screen yet.
|
||||
; Shadowing is turned off. Render all of the scan lines that need a second pass. One
|
||||
; optimization that can be done here is that the lines can be rendered in any order
|
||||
; since it is not shown on-screen yet.
|
||||
|
||||
; jsr _RenderPhaseA ; Draw all of the background lines
|
||||
; jsr _RenderSprites ; Draw all of the sprites
|
||||
|
||||
; ldx #152 ; Blit the full virtual buffer to the screen
|
||||
; ldy #160
|
||||
; jsr _BltRange
|
||||
ldx #0 ; Blit the full virtual buffer to the screen
|
||||
ldy #8
|
||||
jsr _BltRange
|
||||
|
||||
; Turn shadowing back on
|
||||
|
||||
@ -110,31 +106,17 @@ _Render
|
||||
|
||||
; Now render all of the remaining lines in top-to-bottom (or bottom-to-top) order
|
||||
|
||||
; jsr _RenderPhaseB ; Draw the mix of background lines overlays and PEI slams
|
||||
lda ScreenY0 ; pass the address of the first line of the overlay
|
||||
clc
|
||||
adc #0
|
||||
asl
|
||||
tax
|
||||
lda ScreenAddr,x
|
||||
clc
|
||||
adc ScreenX0
|
||||
jsl Overlay
|
||||
|
||||
; ldx #0 ; Expose the top 8 rows
|
||||
; ldy #8
|
||||
; jsr _PEISlam
|
||||
|
||||
; ldx #0 ; Blit the full virtual buffer to the screen
|
||||
; ldy #16
|
||||
; jsr _BltRange
|
||||
|
||||
; ldx #0 ; Blit the full virtual buffer to the screen
|
||||
; ldy #152
|
||||
; jsr _BltRange
|
||||
|
||||
; lda ScreenY0 ; pass the address of the first line of the overlay
|
||||
; clc
|
||||
; adc #152
|
||||
; asl
|
||||
; tax
|
||||
; lda ScreenAddr,x
|
||||
; clc
|
||||
; adc ScreenX0
|
||||
; jsl Overlay
|
||||
|
||||
ldx #0 ; Blit the full virtual buffer to the screen
|
||||
ldx #8 ; Blit the full virtual buffer to the screen
|
||||
ldy ScreenHeight
|
||||
jsr _BltRange
|
||||
|
||||
@ -154,83 +136,3 @@ _Render
|
||||
|
||||
stz DirtyBits
|
||||
rts
|
||||
|
||||
MAX_SEGMENTS equ 128
|
||||
|
||||
PhaseACount ds 0
|
||||
PhaseATop ds 2*MAX_SEGMENTS
|
||||
PhaseABot ds 2*MAX_SEGMENTS
|
||||
PhaseAOp ds 2*MAX_SEGMENTS
|
||||
|
||||
PhaseBCount ds 0
|
||||
PhaseBTop ds 2*MAX_SEGMENTS
|
||||
PhaseBBot ds 2*MAX_SEGMENTS
|
||||
PhaseBOp ds 2*MAX_SEGMENTS
|
||||
|
||||
; Initialize the rendering tree to just render all of the code fields
|
||||
_InitRenderTree
|
||||
lda #1 ; Put the whole screen into Phase B
|
||||
sta PhaseBCount
|
||||
|
||||
lda #0
|
||||
sta PhaseBTop
|
||||
lda ScreenHeight
|
||||
sta PhaseBBot
|
||||
lda #_BltRange
|
||||
sta PhaseBOp
|
||||
|
||||
stz PhaseACount ; Phase A is initially empty
|
||||
rts
|
||||
|
||||
; Solid overlays are called in Phase B, but do not require the screen
|
||||
; to be drawn underneath, so this provides an opportunity to optimize
|
||||
; the rendering pipeline
|
||||
_AddSolidOverlay
|
||||
rts
|
||||
|
||||
; A mixed overlay signals that the underlying scan line data must be
|
||||
; redered first.
|
||||
_AddMixedOverlay
|
||||
rts
|
||||
|
||||
_RenderPhaseA
|
||||
ldy #0
|
||||
:loop
|
||||
cpy PhaseACount
|
||||
bcs :out
|
||||
phy ; save the counter
|
||||
|
||||
lda PhaseAOp,y ; dispatch to the appropriate function
|
||||
sta :op+1
|
||||
ldx PhaseATop,y
|
||||
lda PhaseABot,y
|
||||
tay
|
||||
:op jsr $0000
|
||||
|
||||
ply ; restore the counter
|
||||
iny
|
||||
iny
|
||||
bra :loop
|
||||
:out
|
||||
rts
|
||||
|
||||
_RenderPhaseB
|
||||
ldy #0
|
||||
:loop
|
||||
cpy PhaseBCount
|
||||
bcs :out
|
||||
phy ; save the counter
|
||||
|
||||
lda PhaseBOp,y ; dispatch to the appropriate function
|
||||
sta :op+1
|
||||
ldx PhaseBTop,y
|
||||
lda PhaseBBot,y
|
||||
tay
|
||||
:op jsr $0000
|
||||
|
||||
ply ; restore the counter
|
||||
iny
|
||||
iny
|
||||
bra :loop
|
||||
:out
|
||||
rts
|
||||
|
25
src/Sprite.s
25
src/Sprite.s
@ -399,7 +399,30 @@ _EraseTileSprite
|
||||
|
||||
; Add a new sprite to the rendering pipeline
|
||||
;
|
||||
; A = tileId
|
||||
; 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.
|
||||
; Bits 11 - 13 : Sprite Size Selector
|
||||
; 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)
|
||||
; Bit 14 : Low Sprite priority. Draws behind high priority tiles.
|
||||
; Bit 15 : Reserved. Must be zero.
|
||||
;
|
||||
; 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
|
||||
; X = x position
|
||||
; Y = y position
|
||||
AddSprite ENT
|
||||
|
Loading…
x
Reference in New Issue
Block a user