

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
[…] first variable (CURRENT_LEVEL) is used to point at a specific map. Then the subroutine (LoadLevel) will begin to build the tiles on the screen and place them one by one. Each tile is 4×4 (4 […]