diff --git a/demos/sprites/App.Main.s b/demos/sprites/App.Main.s index daa6b9f..0c041b9 100644 --- a/demos/sprites/App.Main.s +++ b/demos/sprites/App.Main.s @@ -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 \ No newline at end of file diff --git a/macros/CORE.MACS.S b/macros/CORE.MACS.S index da8a16f..0eda7f0 100644 --- a/macros/CORE.MACS.S +++ b/macros/CORE.MACS.S @@ -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 diff --git a/src/Core.s b/src/Core.s index 14ca270..cfdd804 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/Render.s b/src/Render.s index 0801711..a34dcf2 100644 --- a/src/Render.s +++ b/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 diff --git a/src/Sprite.s b/src/Sprite.s index d72dbaa..c13f092 100644 --- a/src/Sprite.s +++ b/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