Week Three
Added two more tables. One for fast conversion from cartesian coordinates to screen address. Another for fast conversion from cartesian coordinates to tile map index because (to save space) my tile map is a straight list with irregular row sizes rather than a sheared grid (ask Tricky
).
Code: Select all
.tblColOffset ; 16 times table. Add tblRowStart to convert cartesian coords
EQUB $00,$10,$20,$30,$40,$50,$60,$70,$80,$90,$A0,$B0,$C0,$D0,$E0
.tblRowTotal ; tileMap index values for first column in each row of map
EQUB 161,152,142,131,119,106,92,77,63,50,38,27,17,8,0
Added a tile clearing mask (so I can redraw over existing tiles).
Code: Select all
.tileMask
EQUW %1111110000111111
EQUW %1111000000001111
EQUW %1100000000000011
EQUW %0000000000000000
EQUW %0000000000000000
EQUW %0000000000000000
EQUW %0000000000000000
EQUW %0000000000000000
EQUW %0000000000000000
EQUW %0000000000000000
EQUW %0000000000000000
EQUW %0000000000000000
EQUW %0000000000000000
EQUW %0000000000000000
EQUW %1100000000000011
EQUW %1111000000001111
Isolated the code that draws a single tile. Made it into a separate subroutine that accepts parameters to pass cartesian coordinates (X and Y) and a tile number (A). Added a few lines to incorporate the clearing mask.
Code: Select all
.redrawTile
; Store A as tile number
STA tileNumber
; Convert X and Y to screen address for drawing
TYA : ASL A : TAY : CLC
LDA tblRowStart,Y : ADC tblColOffset,X : STA screenPointerL
LDA tblRowStart+1,Y : ADC #0 : STA screenPointerH
; Draw the first row of 16 pixels using bitwise OR to mask the corners of
; each tile because they overlap. No need to clear or store in the tilesheet
; because this row is always the same (3 green pixels).
LDA #%00000001 ; first byte in row
LDY #0 : ORA (screenPointerL),Y : STA (screenPointerL),Y
LDA #%11000000 ; second byte in row
LDY #8 : ORA (screenPointerL),Y : STA (screenPointerL),Y
; Increment screen address. No need to worry about carry or
; character block boundary as first row is always even
INC screenPointerL
; Initialise tile byte counter
LDA #32 : STA tileByteCount
; Get start address of tile sheet
LDA #LO(tileSheet) : STA tilePointerL
LDA #HI(tileSheet) : STA tilePointerH
; Load tile number and skip tile sheet offset arithmetic if this is 0
LDA tileNumber : BEQ nextTileRow
; Otherwise multiply by 32 (no. of bytes per tile) to give tile sheet offset
STA tileOffsetL : LDA #0 : STA tileOffsetH : LDX #5
.shiftTileOffset
ASL tileOffsetL : ROL tileOffsetH
DEX : BNE shiftTileOffset
; Add offset to start adddresss of tile sheet to get absolute address of tile
CLC
LDA tilePointerL : ADC tileOffsetL : STA tilePointerL
LDA tilePointerH : ADC tileOffsetH : STA tilePointerH
; Loop through each row of pixels (except the last)
.nextTileRow
; Draw current row of 16 pixels. Clear using bitwise AND and set using
; bitwise OR to mask the corners of each tile because they overlap.
DEC tileByteCount ; first byte in row
LDY tileByteCount : LDA tileMask,Y
LDY #0 : AND (screenPointerL),Y : STA (screenPointerL),Y
LDY tileByteCount : LDA (tilePointerL),Y
LDY #0 : ORA (screenPointerL),Y : STA (screenPointerL),Y
DEC tileByteCount ; second byte in row
LDY tileByteCount : LDA tileMask,Y
LDY #8 : AND (screenPointerL),Y : STA (screenPointerL),Y
LDY tileByteCount : LDA (tilePointerL),Y
LDY #8 : ORA (screenPointerL),Y : STA (screenPointerL),Y
; Skip screen address arithmetic if last row has been reached
LDA tileByteCount : BEQ endTile
; Increment low byte of screen address
INC screenPointerL
; Loop back to draw next row of pixels if new screen address is not
; a multiple of 8 (which means we've crossed a character block
; boundary and may also need to carry)
LDA #%111 : AND screenPointerL : BNE nextTileRow
; Increment high byte of screen address if low byte is now zero
CMP screenPointerL : BNE screenPointerNoCarry
INC screenPointerH
.screenPointerNoCarry
; Add 312 to screen pointer because we have crossed a character
; block boundary and want to move down not right
CLC
LDA #LO(312) : ADC screenPointerL : STA screenPointerL
LDA #HI(312) : ADC screenPointerH : STA screenPointerH
; Loop back to draw next row of pixels
JMP nextTileRow
.endTile
; The last row of pixels now draws itself because this row is always blank
; (1 black pixel) and so we can just end this subroutine here
RTS
Modified drawMap routine to use redrawTile. Slightly slower because of clearing mask (not required on a blank screen) and coordinate conversion but still fast enough. Also added a couple of "wait VSYNC" lines to stop tearing as the colours change. This is now simpler/easier to read.
Code: Select all
.drawMap
; Wait for vertical sync to avoid tearing
LDA #OSBYTE_WAIT_VSYNC : JSR OSBYTE
; Set foreground colour to black so we can't see
LDX #LO(lightsOff) : LDY #HI(lightsOff) : LDA #$C : JSR OSWORD
; Initialise row counter
LDA #14 : STA mapY
; Loop through each row of the map
.nextMapRow
; Initialise column counter
LDY mapY: LDX tblColCount,Y : DEX : STX mapX
; Loop through each column in the row
.nextMapCol
; Convert cartesian coordinates to tile map index
LDY mapY : CLC : LDA tblRowTotal,Y : ADC mapX : TAY
; Load parameters into registers
LDA tileMap,Y : LDX mapX : LDY mapY
; Draw Tile
JSR redrawTile
; Adjust loop counters and branch appropriately
DEC mapX : BPL nextMapCol
DEC mapY : BPL nextMapRow
; Wait for vertical sync to avoid tearing
LDA #OSBYTE_WAIT_VSYNC : JSR OSBYTE
; Reset foreground colour to green to reveal map
LDX #LO(lightsOn) : LDY #HI(lightsOn) : LDA #$C : JSR OSWORD
; Return
RTS
Modified my main loop to allow control of a single mob/sprite. As things stand this hides the underlying tile. Tried masking but in monochrome this doesn't work well. I have a different idea but I'll come back to that.
The other obvious imperfection is that as things stand you can only move in four directions along the cartesian axes. This is where things get interesting because I'm trying to avoid a six key system (which wouldn't work well with gamepads etc). All will be revealed. Hopefully quite soon now that I have a way to indicate mob/sprite positions.
Also added the option to press spacebar. This just forces a full redraw for testing purposes. Basically... It's just temporary.
There is also some code to stop the mob going off the edge but that is uncommented because that too is temporary.
Code: Select all
ORG $3000
.start
; Select screen mode 4
LDA #22 : JSR OSWRCH : LDA #4 : JSR OSWRCH
; Hide cursor
LDA #23 : JSR OSWRCH : LDA #1 : JSR OSWRCH : LDA #0 : JSR OSWRCH
LDX #7 : .zeros1 : LDA #0 : JSR OSWRCH : DEX : BNE zeros1
; Disable cursor editing
LDX #CURSOR_EDITING_OFF : LDA #OSBYTE_SET_CURSOR_EDITING : JSR OSBYTE
; Disable keyboard auto-repeat
LDX #0 : LDA #OSBYTE_SET_KEYBOARD_DELAY : JSR OSBYTE
; Draw map
JSR drawMap
; Draw mob
LDA mobImage : LDX mobX : LDY mobY
JSR redrawTile
.keyboardLoop
; Read keyboard with (almost) zero delay using OSBYTE 129 (aka INKEY)
LDX #0 : LDY #0 : LDA #129 : JSR OSBYTE
; If nothing pressed then keep listening
CPY #255 : BEQ keyboardLoop
.checkEscape ; If Escape pressed then GTFOH
CPY #27 : BNE checkLeft
JMP escape
.checkLeft ; If left arrow pressed then branch accordingly
CPX #136 : BNE checkRight
JMP keyLeft
.checkRight ; If right arrow pressed then branch accordingly
CPX #137 : BNE checkDown
JMP keyRight
.checkDown ; If down arrow pressed then branch accordingly
CPX #138 : BNE checkUp
JMP keyDown
.checkUp ; If up arrow pressed then branch accordingly
CPX #139 : BNE checkSpace
JMP keyUp
.checkSpace ; If spacebar pressed then branch accordingly
CPX #32 : BNE keyboardLoop
JMP keySpace
.keyLeft
LDA mobX : BNE safeLeft
.unsafeLeft
JMP keyboardLoop
.safeLeft
DEC mobX : JMP moved
.keyRight
LDY mobY
LDX mobX : INX
TXA : CMP tblColCount,Y : BCC safeRight
.unsafeRight
JMP keyboardLoop
.safeRight
INC mobX : JMP moved
.keyDown
LDY mobY : BEQ unsafeDown
CPY #8 : BCS safeDown
LDX mobX : INX
TXA : CMP tblColCount,Y : BCC safeDown
.unsafeDown
JMP keyboardLoop
.safeDown
DEC mobY : JMP moved
.keyUp
LDY mobY : CPY #14 : BEQ unsafeUp
CPY #7 : BCC safeUp
LDX mobX : INX
TXA : CMP tblColCount,Y : BCC safeUp
.unsafeUp
JMP keyboardLoop
.safeUp
INC mobY : JMP moved
.moved
; Convert cartesian coordinates to tile map index
LDY mobYold : CLC : LDA tblRowTotal,Y : ADC mobXold : TAY
; Load tile draw parameters for old position into registers
LDA tileMap,Y : LDX mobXold : LDY mobYold
; Draw map tile at old position
JSR redrawTile
; Load tile draw parameters for new position into registers
LDA mobImage : LDX mobX : LDY mobY
; Update old position
STX mobXold : STY mobYold
; Draw mob tile at new position
JSR redrawTile
JMP keyboardLoop
.keySpace
; Redraw all
JSR drawMap
LDA mobImage : LDX mobX : LDY mobY
JSR redrawTile
JMP keyboardLoop
.escape
; Acknowledge Escape condition
LDA #126 : JSR OSBYTE
; Enable cursor editing
LDX #CURSOR_EDITING_ON : LDA #OSBYTE_SET_CURSOR_EDITING : JSR OSBYTE
; Reset keyboard auto-repeat to defaults
LDX #0 : LDA #OSBYTE_SET_KEYBOARD_REPEAT : JSR OSBYTE
; Show cursor
LDA #23 : JSR OSWRCH : LDA #1 : JSR OSWRCH : LDA #1 : JSR OSWRCH
LDX #7 : .zeros2 : LDA #0 : JSR OSWRCH : DEX : BNE zeros2
; Back to BASIC (or whatever)
RTS
No screenshot because it doesn't look much different but here's the ssd for anyone who wants to see the effect of the changes...