CharPadTools.asm

; Tools for integrating CharPad character sets, tiles, and maps 
;========================================================================================= 
; Map Notes: 
; 
; CharPad setup : To make a level map, set up for 256 characters 
; A tile size of 4x4 
; Set number of tiles to 64 
; Set map size to 64 x 32 
; *IMPORTANT* set color to 'per character'

In the subroutine (LoadLevel) below we first set the variable (CURRENT_LEVEL_NUMBER). Then we access the character data (in the variable (CHAR_ADDRESS) and write it into into $8000 (below VIC’s memory). The character data now exists in ZEROPAGE_POINTER_1 (low and high bytes).

Next we load our variable (CHAR_MEM) into ZEROPAGE_POINTER_2 (low and high bytes) and we call the subroutine (CopyChars) to copy it to our character set.

After this we access the table (MAP_ADDRESS) to copy the map binary data to ZEROPAGE_POINTER_1 (low and high bytes).  Then the map data is accessed from the variable (MAP_MEM) and copied to ZEROPAGE_POINTER_2 (low and high bytes) and finally copied to the character set.

Examining the two lines below will show what data we have copied to our zero page pointers.

This references CHAR_MEM (as seen in the file Main.asm)

incbin“Parkour_Maps/Parkour Redo Chset6.bin” ; Reads the character set from CharPad

This references MAP_MEM (as seen in the file Main.asm)

incbin“Parkour_Maps/Parkour Redo Map6.bin” ; Reads the map from CharPad

LoadLevel

ldx CURRENT_LEVEL ; Load current level number  
dex ; subtract 1 (levels start at 1 not 0)  
txa  
lsr ; multiply by 2 to get a word offset  
tax ; put it in x to lookup our table 
sta PARAM5 ; save in scratch param (5)
; Here we load all level data into the area below VIC mem ($8000) 
; To setup the level
@loadBanked
sei 
lda PROC_PORT 
sta PARAM1 
lda #%00110000 ; Switch out BASIC, KERNAL, CHAREN, IO 
sta PROC_PORT 
ldx PARAM5 
lda CHAR_ADDRESS,x 
sta ZEROPAGE_POINTER_1 
lda CHAR_ADDRESS + 1,x 
sta ZEROPAGE_POINTER_1 + 1
loadPointer ZEROPAGE_POINTER_2, CHAR_MEM 
jsr CopyChars 
ldx PARAM5 
lda MAP_ADDRESS,x 
sta ZEROPAGE_POINTER_1 
lda MAP_ADDRESS + 1,x 
sta ZEROPAGE_POINTER_1 + 1 
loadPointer ZEROPAGE_POINTER_2, MAP_MEM 
jsr CopyChars 
lda PARAM1 
sta PROC_PORT 
cli 
rts
; Draw the entire map on the screen. This won't be done that often as most updates 
; to the screen will be scrolling. But for starting a level, resetting on death, 
; or teleporting, we need to build the entire screen. 
; This also sets up essential data for using the map. 
; Initializes : MAP_POS_X ; MAP_POS_Y 
; MAP_POS_ADDRESS ; MAP_X_DELTA 
; MAP_Y_DELTA 
; 
; X = Start map X coord (top left corner) 
; Y = Start map Y coord (top left corner) ; 
; Uses ZEROPAGE_POINTER_4 (as TileDraw uses 1,2,3)

First we clear out all the map delta X/Y values (MAP_X_DELTA, MAP_Y_DELTA) and the map position X/Y values (MAP_X_POS, MAP_Y_POS) so no garbage data remains.

Then we locate the starting map address position from our table (MAP_LINE_LOOKUP_LO) which has been created to read data into specific lines at the screen counting from the top of the screen to the bottom. Then the current map address is copied into ZEROPAGE_POINTER_4 (low and high bytes).

DrawMap

lda #0 
sta MAP_X_DELTA 
sta MAP_Y_DELTA 
stx MAP_X_POS 
sty MAP_Y_POS
; First find the address for the starting map position 

ldx MAP_Y_POS 
lda MAP_LINE_LOOKUP_LO,x ; fetch the address for the line (Y pos) 
sta ZEROPAGE_POINTER_4 
lda MAP_LINE_LOOKUP_HI,x 
sta ZEROPAGE_POINTER_4 + 1 
clc 
lda ZEROPAGE_POINTER_4 ; add the x position 
adc MAP_X_POS 
sta ZEROPAGE_POINTER_4 
lda ZEROPAGE_POINTER_4 + 1 
adc #0 
sta ZEROPAGE_POINTER_4 + 1 ; ZEROPAGE_POINTER_1 now holds the map start address 
; Save this info for map usage 
copyPointer ZEROPAGE_POINTER_4, MAP_POS_ADDRESS

Now that we have stored away our map data information, we can begin to draw it on the screen. First we read from ZEROPAGE_POINTER_4 (low byte) and call subroutine (DrawTile) which essentially builds 4×4 tiles that can be placed on the screen in rows (see below).

After we have placed the tiles, we continue going over 10 rows and go down 5, which is the maximum we can fit since each single tile block holds 16 tiles (in a 4 x 8 matrix). Look at a CharPad 4×4 tile map and you will see how this works. I have also added a screenshot below that shows the grid around the tiles for a better perspective.

; Fetch map data and draw tile - coords are in 'tiles' not character positions 

ldy #0 ; holds X screen coord 
ldx #0 ; holds Y screen coord 
@loop 
lda (ZEROPAGE_POINTER_4),y ; fetch map data 
jsr DrawTile ; draw the tile 
iny ; inc X and check for end of screen 
cpy #10 ; (10 tiles) 
bne @loop ; go down one line on the map (64 char) 
addPointer ZEROPAGE_POINTER_4, 100 
ldy #0 
inx 
cpx #5 
bne @loop 
rts

I created this grid map example so you can hopefully more visually see how the tiles are arranged on the screen. Although the tiles don’t align perfect here you can see though that they stretch out 10 across and 5 down. As we saw in the Tile Editor example above, each red square will occupy our tile (made up of 16 grid blocks each).

; X = Screen tile Y coord - 'flipped' so it dovetails into the MapDraw 
; Y = Screen tile X coord routine without exchanging data in registers 
; A = Tile # to draw ; ; Restores registers off the stack A / X / Y 
; 
; Restores registers off the stack A / X / Y
;-------------------------------------------------------------------------

Now we load the accumulator with the data we had earlier in our ZEROPAGE_POINTER_4 (low and high bytes) area, which contains our map data from CharPad loaded into the CBM Prg Studio Editor to be placed into the variable (PARAM1).

Then we transfer our X register into (PARAM2) and Y register into (PARAM3). After saving them on the stack, we then load in our Y register and search for a screen address to place a tile into by calling the subroutine (FetchScreenLineAddress). If you click on the link there it will open a new tab where you can observe how memory is copied here. Essentially it reads from the current selected screen (Screen1 or Screen2) and saves that data into ZEROPAGE_POINTER_1 (low and high bytes).

DrawTile

sta PARAM1 ; save tile number
sty PARAM2 ; save X pos 
stx PARAM3 ; save Y pos
saveRegs ; put registers on the stack to
; exit cleaner - this routine will 
; likely be nested 
;------------------------------------------------------ 
; First get the destination for the tile 
lda PARAM3 ; fetch the Y pos (in tile coords) 
asl 
asl ; multiply by 4 (tiles are 4 x 4 chars) 
tax ; screen line in X 
jsr FetchScreenLineAddress ; fetch line address based on current displayed screen
; Y line address is in ZEROPAGE_POINTER_1

After this we load from the table (COLOR_LINE_OFFSET_TABLE_LO/HI) to read the correct color values onto our tile and copy them into ZEROPAGE_POINTER_3 (low and high bytes).

Next we access the X register and multiply it by 4 to stretch the tile 4 across. The color map data is then saved into ZEROPAGE_POINTER_1 (low and high bytes)  to be referenced later by a call to SCREEN1 (SCREEN1_LINE_OFFSET_TABLE_LO/HI) bytes or SCREEN2 (SCREEN2_LINE_OFFSET_TABLE_LO/HI) bytes.

Then we get the current screen line (saved in the X register) and add it to our variable (ZEROPAGE_POINTER_1) which we know contains our tile information and then continue to the next line as each tile is built down the screen.

lda COLOR_LINE_OFFSET_TABLE_LO,x ; fetch color ram line address too
sta ZEROPAGE_POINTER_3 
lda COLOR_LINE_OFFSET_TABLE_HI,x 
sta ZEROPAGE_POINTER_3 + 1
lda PARAM2 ; get X coord
asl ; multiply by 4
asl 
tax ; save it in x 
clc ; add to Y line address
adc ZEROPAGE_POINTER_1
sta ZEROPAGE_POINTER_1
lda ZEROPAGE_POINTER_1 + 1 
adc #0 
sta ZEROPAGE_POINTER_1 + 1 ; destination base address is in ZEROPAGE_POINTER_1

Going forward let’s now get the color ram data and place it into ZEROPAGE_POINTER_3 (low and high bytes) and continue down the screen.

txa 
clc 
adc ZEROPAGE_POINTER_3 ; color ram destination is in ZEROPAGE_POINTER_3 
sta ZEROPAGE_POINTER_3 
lda ZEROPAGE_POINTER_3 + 1 
adc #0 
sta ZEROPAGE_POINTER_3 + 1

In this section we access the variable (PARAM1) which contains our map data screen line from earlier and then load into memory our variable (TILE_NUMBER_LOOKUP_LO/HI) bytes and save this tile data into ZEROPAGE_POINTER_2 (low and high bytes).

;------------------------------------------------------------  
; Fetch the source tile address  
ldx PARAM1 
; Fetch the tile number  
lda TILE_NUMBER_LOOKUP_LO,x 
sta ZEROPAGE_POINTER_2 
lda TILE_NUMBER_LOOKUP_HI,x 
sta ZEROPAGE_POINTER_2 + 1

We now begin a new loop and get the data in (ZEROPAGE_POINTER_2) which we copied our tile data to earlier and begin to draw it on the screen using ZEROPAGE_POINTER_1 which represents the screen memory area to write it to.

Then we need to load the variable (ATTRIBUTE_MEM) which contains the Character Set Attribute data loaded into memory in the Main.asm file. This can be understood by looking at the line below.

ATTRIBUTE_MEM

incbin“Parkour_Maps/Parkour Redo ChsetAttrib6.bin”

The attribute data from the binary file above will then be copied to ZEROPAGE_POINTER_3 which writes the attribute to the color map data contained in the background, which will be used later to reference collisions whenever our Sprite passes over an area containing attribute data contained in the CharPad attribute area.  There are 15 characters that need to be copied.

After this the Y register is used to count through 0-15 characters and count from 0-3 to draw the tiles on the screen. To break this down more, the counting from 0-15 is necessary since there are 15 total attributes.  Continue this loop until all tiles are accounted for. The counting will quickly count from 0-3 and then increment to the next attribute to successfully get all of the right color values.  So if you take 15 and divide it by 3 you will get 5 (as there are 5 total areas to access here).


; Loop through and draw the tile  
ldy #0 @drawloop 
lda (ZEROPAGE_POINTER_2),y ; Get the character code 
sta (ZEROPAGE_POINTER_1),y ; store it on the screen 
tax ; pass to X as an offset 
lda ATTRIBUTE_MEM,x ; fetch the color/data attribute 
;  
sta (ZEROPAGE_POINTER_3),y ; write it to color ram 
cpy #15 ; drawn the 15th character? We're finished 
beq @done 
tya ; save Y before the increment for our test 
iny ; (saves having to 
inc it in 2 diff places) ; I need to count 0-3 to draw a row of tiles,  
and #%00000011 ; but I need to count 0-15 to fetch the tile data 
cmp #3 ; both NEED to use indirect Y addressing, and saving/ 
bne @drawloop ; fetching Y rapidly becomes a tangled nightmare. 
; by masking out the last 2 bits in A, we get a number 
; that counts 0-3 over and over without stopping the 
; data fetch count 0-15. It's also faster than my other 
; options by quite a bit.

Note: The Materials area seen in the picture on the left in CharPad presents the color map information that is copied into memory so that the collisions can be detected on the map. The appropriate number (0-9, A-F) indicates the action being taken. If you look also off to the right area of the materials section (where the numbers are seen on the map) and notice the “1”‘ values on the side, just know that these are used to detect when the Player sprite has bumped into a wall (tile).

Compare the two images to understand color attribute memory better. The first one represents the attribute data seen on a map area (by the river). The second one shows the map as you would see it in the game. The 4’s seen in the first image are used by the game to check a tile that contains that color map information to perform an action. So later when you get to the section “C64 Assembly Project 2020 Player Setup” you will learn how a condition can be set to check if the Player has landed in the water. If you want, as usual you can click on the link to skip ahead to that part. Just be sure to return back to this page to continue the lesson here.

The remaining lines seen below will continue to color our tile data to the map using ZEROPAGE_POINTER_1 (low and high bytes). For the color data will continue to pull the attribute data and place it into ZEROPAGE_POINTER_3 (low and high bytes). Finally we continue down the screen (using the adc #40 -4) and keep drawing in the line over and over.

All of the following table data below is referenced in the code on this page and is here for easy reference.

clc ; add new line 
lda ZEROPAGE_POINTER_1 ; increment destination and color ram by 1 line - 4 chars 
adc #40 - 4 
sta ZEROPAGE_POINTER_1 ; by 'backsetting' our pointers, we don't need to change Y 
lda ZEROPAGE_POINTER_1 + 1 ; when drawing 0-3 characters, and can leave the 0-15 
adc #0 ; count intact. 
sta ZEROPAGE_POINTER_1 + 1 ; We have to increase them to the next line anyways 
clc 
lda ZEROPAGE_POINTER_3 
adc #40 - 4 
sta ZEROPAGE_POINTER_3 
lda ZEROPAGE_POINTER_3 + 1 
adc #0 
sta ZEROPAGE_POINTER_3 + 1 
jmp @drawloop 
@done 
restoreRegs ; pull registers back off the stack  
rts
; Dummy 1st level values for now, to make it easier to incorporate more levels and loading later 
;--------------------------------------------------------------------------------------------------- 
CURRENT_LEVEL byte 0 
CHAR_ADDRESS word LEVEL_1_CHARS 
ATTRIB_ADDRESS word ATTRIBUTE_MEM 
TILE_ADDRESS word TILE_MEM 
MAP_ADDRESS word LEVEL_1_MAP
MAP_LINE_LOOKUP_LO
byte <MAP_MEM byte 100 
byte 200 
byte 300 
byte 400 
byte 500 
byte 600
byte 700 
byte 800 
byte 900 
byte 1000 ; 10 
byte 1100 
byte 1200 
byte 1300 
byte 1400 
byte 1500 
byte 1600 
byte 1700 
byte 1800 
byte 1900 
byte 2000 ;20 
byte 2100 
byte 2200 
byte 2300 
byte 2400 
byte 2500 
byte 260
byte 2700 
byte 2800 
byte 2900 
byte 3000 ;30 
byte 3100 
byte 3200 ;32 ;
; New lines - Parkour Big Map 
byte 3300 
byte 3400 
byte 3500 
byte 3600 
byte 3700 
byte 3800 
byte 3900 
byte 4000 
byte 4100 
byte 4200 
byte 4300 
byte 4400 
byte 4500 
byte 4600 
byte 4700 
byte 4800 
byte 4900 
byte 5000 
byte 5010 
byte 5020 
byte 5030 
byte 5040 
byte 5050
MAP_LINE_LOOKUP_HI
byte >MAP_MEM 
byte >MAP_MEM + 100 
byte >MAP_MEM + 200 
byte >MAP_MEM + 300 
byte >MAP_MEM + 400 
byte >MAP_MEM + 500 
byte >MAP_MEM + 600 
byte >MAP_MEM + 700 
byte >MAP_MEM + 800 
byte >MAP_MEM + 900 
byte >MAP_MEM + 1000 ; 10 
byte >MAP_MEM + 1100 
byte >MAP_MEM + 1200 
byte >MAP_MEM + 1300 
byte >MAP_MEM + 1400 
byte >MAP_MEM + 1500 
byte >MAP_MEM + 1600 
byte >MAP_MEM + 1700 
byte >MAP_MEM + 1800 
byte >MAP_MEM + 1900 
byte >MAP_MEM + 2000 ;20 
byte >MAP_MEM + 2100 
byte >MAP_MEM + 2200 
byte >MAP_MEM + 2300 ; 
byte >MAP_MEM + 4700 
byte >MAP_MEM + 2400 
byte >MAP_MEM + 2500 
byte >MAP_MEM + 2600 
byte >MAP_MEM + 2700 
byte >MAP_MEM + 2800 
byte >MAP_MEM + 2900 ;30 
byte >MAP_MEM + 3000 
byte >MAP_MEM + 3100 ;32 
;
; New lines - Parkour Big Map 
byte >MAP_MEM + 3200 
byte >MAP_MEM + 3300 
byte >MAP_MEM + 3400 
byte >MAP_MEM + 3500 
byte >MAP_MEM + 3600 
byte >MAP_MEM + 3700 
byte >MAP_MEM + 3800 
byte >MAP_MEM + 3900 
byte >MAP_MEM + 4000 
byte >MAP_MEM + 4100 
byte >MAP_MEM + 4200 
byte >MAP_MEM + 4300 
byte >MAP_MEM + 4400 
byte >MAP_MEM + 4500 
byte >MAP_MEM + 4600 
byte >MAP_MEM + 4700 
byte >MAP_MEM + 4800 
byte >MAP_MEM + 4900 
byte >MAP_MEM + 5000 
byte >MAP_MEM + 5010 
byte >MAP_MEM + 5020 
byte >MAP_MEM + 5030 
byte >MAP_MEM + 5040 
byte >MAP_MEM + 5050
TILE_NUMBER_LOOKUP_LO 
byte <TILE_MEM ; 0 
byte <TILE_MEM + 16 
byte <TILE_MEM + 32 
byte <TILE_MEM + 48 
byte <TILE_MEM + 64 
byte <TILE_MEM + 80 
byte <TILE_MEM + 96 
byte <TILE_MEM + 112 
byte <TILE_MEM + 128 
byte <TILE_MEM + 144 
byte <TILE_MEM + 160 ; 10 
byte <TILE_MEM + 176 
byte <TILE_MEM + 192 
byte <TILE_MEM + 208 
byte <TILE_MEM + 224 
byte <TILE_MEM + 240 
byte <TILE_MEM + 256 
byte <TILE_MEM + 272 
byte <TILE_MEM + 288 
byte <TILE_MEM + 304 
byte <TILE_MEM + 320 ; 20 
byte <TILE_MEM + 336 
byte <TILE_MEM + 352 
byte <TILE_MEM + 368 
byte <TILE_MEM + 384 
byte <TILE_MEM + 400 
byte <TILE_MEM + 416 
byte <TILE_MEM + 432 
byte <TILE_MEM + 448 
byte <TILE_MEM + 464 
byte <TILE_MEM + 480 ; 30 
byte <TILE_MEM + 496 
byte <TILE_MEM + 512 
byte <TILE_MEM + 528 
byte <TILE_MEM + 544 
byte <TILE_MEM + 560  
byte <TILE_MEM + 576 
byte <TILE_MEM + 592 
byte <TILE_MEM + 608 
byte <TILE_MEM + 624 
byte <TILE_MEM + 640 ; 40 
byte <TILE_MEM + 656 
byte <TILE_MEM + 672 
byte <TILE_MEM + 688 
byte <TILE_MEM + 704 
byte <TILE_MEM + 720  
byte <TILE_MEM + 736 
byte <TILE_MEM + 752 
byte <TILE_MEM + 768 
byte <TILE_MEM + 784 
byte <TILE_MEM + 800 ;50 
byte <TILE_MEM + 816 
byte <TILE_MEM + 832 
byte <TILE_MEM + 848 
byte <TILE_MEM + 864 
byte <TILE_MEM + 880 
byte <TILE_MEM + 896 
byte <TILE_MEM + 912 
byte <TILE_MEM + 928 
byte <TILE_MEM + 944 
byte <TILE_MEM + 960 ; 10 
byte <TILE_MEM + 976 
byte <TILE_MEM + 992 
byte <TILE_MEM + 1008 
byte <TILE_MEM + 1024 ; 64
TILE_NUMBER_LOOKUP_HI 
byte >TILE_MEM ; 0 
byte >TILE_MEM + 16 
byte >TILE_MEM + 32 
byte >TILE_MEM + 48 
byte >TILE_MEM + 64 
byte >TILE_MEM + 80 
byte >TILE_MEM + 96 
byte >TILE_MEM + 112 
byte >TILE_MEM + 128 
byte >TILE_MEM + 144 
byte >TILE_MEM + 160 ; 10 
byte >TILE_MEM + 176 
byte >TILE_MEM + 192 
byte >TILE_MEM + 208 
byte >TILE_MEM + 224 
byte >TILE_MEM + 240 
byte >TILE_MEM + 256 
byte >TILE_MEM + 272 
byte >TILE_MEM + 288 
byte >TILE_MEM + 304 
byte >TILE_MEM + 320 ; 20 
byte >TILE_MEM + 336 
byte >TILE_MEM + 352 
byte >TILE_MEM + 368 
byte >TILE_MEM + 384 
byte >TILE_MEM + 400 
byte >TILE_MEM + 416 
byte >TILE_MEM + 432 
byte >TILE_MEM + 448 
byte >TILE_MEM + 464 
byte >TILE_MEM + 480 ; 30 
byte >TILE_MEM + 496 
byte >TILE_MEM + 512 
byte >TILE_MEM + 528 
byte >TILE_MEM + 544 
byte >TILE_MEM + 560  
byte >TILE_MEM + 576 
byte >TILE_MEM + 592 
byte >TILE_MEM + 608 
byte >TILE_MEM + 624 
byte >TILE_MEM + 640 ; 40 
byte >TILE_MEM + 656 
byte >TILE_MEM + 672 
byte >TILE_MEM + 688 
byte >TILE_MEM + 704 
byte >TILE_MEM + 720  
byte >TILE_MEM + 736 
byte >TILE_MEM + 752 
byte >TILE_MEM + 768 
byte >TILE_MEM + 784 
byte >TILE_MEM + 800 ;50 
byte >TILE_MEM + 816 
byte >TILE_MEM + 832 
byte >TILE_MEM + 848 
byte >TILE_MEM + 864 
byte >TILE_MEM + 880 
byte >TILE_MEM + 896 
byte >TILE_MEM + 912 
byte >TILE_MEM + 928 
byte >TILE_MEM + 944 
byte >TILE_MEM + 960 ; 10 
byte >TILE_MEM + 976 
byte >TILE_MEM + 992 
byte >TILE_MEM + 1008 
byte >TILE_MEM + 1024 ; 64