Creating and displaying game maps
In this section you are going to learn how to add sprite graphics to your game project, generate maps for your game, read a character set, add 4 x 4 tiles to your project, flip between the front and back screens, and remove the color bleed problem.
I
n order to get a better focus on scrolling, Siggy mentioned that it was important to have a working map first. We opted to use CharPad since it is much easier to implement map raw data into CBM Prg Studio for our Commodore 64 Machine Language Project. For this example, we used CharPad 2.0, which was the latest update in the series.
It is pretty identical to the earlier version we were using, as it contains also a Character Set, Tile Set, Tile Editor, Char Editor, and Map windows. The functionality is the same so I won’t repeat how it works, but you are welcome to check out Part 4 for a more in-depth explanation.
Siggy created some simple fonts to use for the project currently. He created a simple display of letters, just to test the map to ensure that the 4 x 4 tiles are “going into the right place”, as he stated. This makes it easier to debug.
A discovery was made using the Materials option built into CharPad 2.0 where you can assign these to each character. Setting the Colouring method to Per Character, he was able to assign a material to each character. This allows each to have its own color. Colors range from 0-16 ($00 – $0F). It uses the low nybble of the byte. So color ram has an entire nybble we are not using. Siggy said, “I believe that is where these colors come in” (referring to the Materials panel).
So he can set the “A” character, for example and give it a material of “1”. In our program it could be pulled directly off the screen and assigned to something. “If I wanted A to be a platform”, Siggy advised as an example. Whatever tile A is in that material would be there. This can then be pulled directly off the color ram screen. This will allow us to look at the color ram in the upper nybble to provide the correct character matching its material.
Loading Sprites and Maps
“Eventually we are going to be loading all of our stuff in binary raw format”, Siggy conveyed. This will be contained in memory for reference. To start out with we are going to have an area in ram which is where we are going to place our current map. The map will be loaded from different parts of memory into one area when they are being used. Variables can be setup to look at this area. This also will allow a map to be reloaded from the start once the game is over and someone wants to play again.
The sprites are loaded in using the incbin command in CBM Prg Studio, which is referred to as a ‘binary load’. This can set a starting and ending address for the load. So for example, our game sprites are loaded with incbin “Sprites.spt”,1, 4, true, which will load sprites 20-23. Sprites 0-20 were used to load the Bezerk character created in Part 1 with incbin “Killbot.spt”,1, 20, true.
The level data is loaded in using incasm “CharPadData.asm”. This will load the individual assembly file, rather than the binary. He placed the starting address for the level data at $9000 (36864). This occupies around 2k of memory. The memory reserved here will contain our character set, colors, attributes, 50 tiles, and 4 x 5 screens of maps.
To keep track of the VIC Bank data, Siggy started working on a routine that would keep track of data. It works by accepting input in areas such as the VIC Bank number (such as 0-3), screen number, and character number that would provide data that could be worked with. As an example he setup the bank size, VIC memory base, and screens 0,1 & 6 that can provide results to our program later.
Sprite Pointers and Back buffers
Next Siggy went into an explanation of how the sprite pointers were being used in the program. The sprite pointers have to be updated for each new screen because when the back buffer is displayed, the sprite pointers are changed. For now though he stated he was going to leave this intact because when the sprite changed to a rectangle it was an indicator that he is now showing the back buffer. This is another debugging tactic that he used.
Test Level Map
A level map was implemented that could be tested out. The map has to be loaded, put in the right memory position, and then we draw it. This is done by pulling tiles from the Tile map, the characters are extracted, and then the next tile is read. This is repeated until the screen is filled up. It is also important to know the start point on the map is located.
Examining the code below, MAP_TILE_POS_X is the position on the screen in tile coordinates. “Every 4 x 4 tile on the screen is read across and then 10 down is read”, Siggy mentioned. Tiles have their own coordinate system. These variables represent where each level will start. However, you need enough space around the starting position to draw the screen. The map will give you the tiles that are drawn on the screen. The tiles are made up of characters. We go about 4 levels deep using lookup tables. The variables MAP_POS_X and MAP_POS_Y are the actual X and Y positions on the map. This is drawn before anything else.
“When we are scrolling we are going to have to keep a buffer of the next characters to be drawn in any direction”, Siggy expressed. “We have to go into the map, then the tile, pulling in those row of characters in and then rebuilding them in a buffer. This is so we can copy them onto the screen we the frame update is executed”, said Siggy. He had scrolling setup to start in the middle
- lda #0 ; set ‘on screen’ coords for map
- sta MAP_TILE_POS_X ; these will be replaced with a ‘start position’
- lda #0
- sta MAP_TILE_POS_Y ; in the level data
- sta MAP_POS_X
- sta MAP_POS_Y
- jsr DrawMap
Running the program, Siggy explained how the process works. Looking along the edge of the right screen you can see the character cuts off, rather than showing the full character for that position. This is seen in 22:00. He scrolled the screen across 3 pixels to center it on a character. Scrolling can be down left or right. There are 3 frames to setup the jump. One of the jump frames has to copy the entire color ram, and has to jump the screen across. This is down by writing to the back buffer. Then the back buffer is shown. One frame copies the color ram. Another one is used to copy the screen onto the back buffer. He said he still has “one frame there”, which is used to pull out all the characters to show on the line at the far left, far right, top, or bottom when he does the jump in the scroll.
Siggy said he finally fixed the color bleed issue that agitated him. This occurred because of the tiles drawn down the screen caused the last line at the bottom to not be shown. The flicker was the result of the raster not being stabilized.
CharPad Tools data
Another file called CharPad1Tools.asm was created to put the tools to use files from CharPad.
The map data was created with a label called charset_data. This contains all 256 characters. Then he setup an area called charset_attrib_data that gives 256 bytes of color and attribute. The main color can be setup for each of these. The standard set is contained in the area of 0-8 and multicolor starts at 9-15.
Next he made an area for the tile set called tileset_data. Each row is 16 bytes using 4 x 4 tiles. This section contains the characters that will be looked up for the display. The blank tile is represented by the ‘@’ symbol.
The map is read using the label called map_data. It points to the tile numbers. As an example, tile $00 is the ‘@’ character. These are laid out individually on the map from the left to the right in rows.
In the next stage, Siggy educated us about the variables that were used to calculate the map and tile coordinates. The SZ_CHARSET_DATA represents the size of the character set data and color attributes. The SZ_CHARSET_ATTRIB_DATA reserves 256 bytes for the color. The variables SZ_TILESET_DATA and SZ_MAP_DATA track the size of the tile set data and the size of the map data. This can be altered as the map is expanded. The varaibel SZ_TOTAL provides the total size of the character set, color data, tile set data, and map data. The character set data is set with ADDR_CHARSET__DATA, which is currently set at $9000. The rest of the variables help us find our data. Once all of the calculations are complete, Siggy said he used several other variables, TILE_MEM, TILE_COLOR_MEM, and LEVEL_MAP_DATA that would be “better names he could use”.
TestMapData explained
This routine was written to lookup different bytes that could be tested against. Data can actually be read from charset_data table. This was another debugging algorithm used to ensure that the map was generating the correct output. In the code below, Siggy algorithm would look up bytes. He stated thought “If it didn’t do it, I would have it to break (BRK)”. This would let him know if the reading of the bytes was successful or a fail.
- TestMapData
- rts ; comment out to perform test
- ldx #$fe
- lda #$ff
- jsr DrawTile
- cpx #$fe
- beq @cont
- brk
- rts
- @cont
- rt
Reading the current level
The LoadMap routine is used to get the current level address, load the record the result in CURRENT_LEVEL, and read the levels character set into memory at $4800, which is set 1 of the VIC Bank. Following the code, below it reads the level number and saves it for later since Siggy said it will be “used again”. Then it is multiplied by 2 (known as ‘bit shifting’ to the left). It does this since there is a table that provides the address where the level is stored in MAP_ADDRESS.
- lda LEVEL_NUMBER ; Get the number of the map to load
- sta PARAM1 ; Save a copy for later
- asl ; multiply by 2 so it looks up the correct word address
- tax ; store in X
- lda MAP_ADDRESS, x ; fetch first byte of address
- sta CURRENT_LEVEL ; store as CURRENT_LEVEL
- sta ZEROPAGE_POINTER_1 ; save in ZEROPAGE_POINTER_1 to load charset
- inx
- lda MAP_ADDRESS, x ;fetch second byte of address
- sta CURRENT_LEVEL + 1
- sta ZEROPAGE_POINTER_1 + 1
- jsr CopyChars ; copy character set to VIC Bank Chars 1
- ; ———————————————————————–
- ; Load various information for the level into ‘current level’ variables
- ldx PARAM1 ; restore the level number to x
- lda MAP_TILE_WIDTH, x ; save the level width and height
- sta LEVEL_TILE_WIDTH
- lda MAP_TILE_HEIGHT, x
- sta LEVEL_TILE_HEIGHT
- rts
MAP_ADRESS explained
If the address is $9000 it is zero. However if there is another level, it could be located elsewhere in memory. For now this left at $000 since no other levels have been created yet.
- MAP_ADDRESS word $9000, $0000
Next the result the calculation from PARAM1 is saved in the x register. This is because the program will be using indirect addressing, using absolute addressing that is indexed. Then MAP_ADDRESS is looked up, which is offset by the x register. Siggy provided an address of how the table lookup works. “So if it is zero it’s going to read whatever is at MAP_ADDRESS”. If it is level 1, it will jump forward 2 bytes and read the next address. A word in an address is 2 bytes.
After this the MAP_ADDRESS is stored in CURRENT_LEVEL and ZEROPAGE_POINTER_1. Then the x register is incremented so it is pointing to the next byte. Now it is pointing to the high byte (MAP_ADDRESS_1) to make up the complete pointer. It is then stored in CURRENT_LEVEL + 1 and ZEROPAGE_POINTER_1 + 1.
Read the C64 Character Set in memory
From this moment, the program jumps the subroutine called CopyChars . It’s copying the character set from the level into the character set used in the VIC Bank, set aside for the screen. The scoreboard screen has its own character set.
- CopyChars
- loadPointer ZEROPAGE_POINTER_2, MAP_CHAR_MEM
- pha ; save contents of A
- ldx #$00 ; clear X, Y, A and PARAM2
- ldy #$00
- lda #$00
- sta PARAM2
- @NextLine
- lda (ZEROPAGE_POINTER_1), Y ; copy from source to target
- sta (ZEROPAGE_POINTER_2), Y
- inx ; increment x / y
- iny
- cpx #$08 ; test for next character block (8 bytes)
- bne @NextLine ; copy next line
- cpy #$00 test for edge of page (256 wraps back to 0)
- bne @PageBoundryNotReached
Further down in the code, Siggy said he restored the level to the x register (in ldx PARAM1). Only one byte has to be read at a time here, since they are not words. Nothing needs to be skipped over. The program then gets the MAP_TILE_WIDTH, offset by x. This is working the same as the MAP_ADDRESS table, but focusing on 1 byte at a time. This is the size of the map in tiles. As an example, Siggy clarified this as ’40 tiles width by 40 tiles high”. Which equates to 4 screens in width and 4 screens in height.
- MAP_TILE_WIDTH byte 40, 0
- MAP_TILE_HEIGHT byte 20, 0
Understanding DrawMap
Now the level map is drawn. It is stored in a variable called LEVEL_NUMBER, starting in MAP_TILE_X and MAP_TILE_Y. Then it is copied to the backbuffer. Siggy says this lets him draw any rectangular region of the map as our “start point”.
Next the LEVEL_TILE_WIDTH is loaded. This data was pulled when we were reading the current level. He says this code is the “multiplier”. To explain further he said, “When we are dealing with the character set, the attributes, and even when we are dealing with the tile setup, the maps are all known sizes and they are known widths”. This tells the program how to get to a specific character in the character data. If he needs to change it, he knows how to get to an attribute. There is always going to be 256 bytes in its memory location.
DrawMap
- ; First we need to set up a pointer to the correct start position in
- ; the map data – because we aren’t using a set map width, we can’t
- ; make a table to work out the screen line.
- lda LEVEL_TILE_WIDTH ; Store level width in param 1 & 2 (word length – 16 bit result)
- sta PARAM1 ; Width goes in low byte (maps aren’t bigger than 255)
- lda #0 ; zero out high byte
- sta PARAM2 ; in PARAM2
- lda MAP_TILE_POS_Y ; Multiply by MAP_TILE_POS_Y
- sta PARAM3
- jsr Multiply16 ; Returns 16bit result in PARAM1 (lo) and PARAM2 (hi)
- lda #
- sta ZEROPAGE_POINTER_4 ; using ZEROPAGE_POINTER_4 because
- lda #>map_data ; it’s not used by TileDraw
- sta ZEROPAGE_POINTER_4 + 1
- clc
- lda ZEROPAGE_POINTER_4 ; Add the offset so ZEROPAGE_POINTER_4 now
- adc PARAM1 ; points to the correct map line
- sta ZEROPAGE_POINTER_4
- lda ZEROPAGE_POINTER_4 + 1
- adc PARAM2
- sta ZEROPAGE_POINTER_4 + 1
The tiles are 16 bytes per tile. The tables TILE_DATA_OFFSET_LO and TILE_DATA_OFFSET_HI was written the same as he did for the offset screens. ‘X’ is the line number. Using the x register, these tables will look up the line number for the tiles. This will tell the correct place in memory to look for the tile. Because the map changes width, he doesn’t know how wide it is. Therefore, a table can’t be made for every outcome. This would result in 64k full of tables and 3 bytes of the program.
Because the 6510 does not have a multiply on board, it takes time when multiplying in 16k. Rather it has bit shifters that can go up or down by powers of two. So we need to find the line of data we need to look up for the map.
The LEVEL_TILE_WIDTH is loaded which is multiplied by the number we need. Problems can occur though since the tile data is big. “Some complications could be it crossing a page border or it could be a really big number”, says Siggy. Therefore “we have to write a multiplier routine” Siggy mentioned.
The width of the map goes into the low byte because maps are not bigger than 255. Then the high byte is zeroed out and stored in PARAM2 (lda #0, sta PARAM2). A high byte is needed because the answer returned may be bigger than 1 byte. Providing an example Siggy said, “If I’m looking up line 160 in LEVEL_TILE_WIDTH, and he has a map width of 100, that will be bigger than 255”. Then MAP_TILE_POS_Y is extracted and stored in PARAM3.
The subroutine data obtained from Multiply16 will feed us back the calculations. This will be used as the offset for a point on the map. The pointers for map_data are then placed in ZEROPAGE_POINTER_4, low and high bytes. This will point to the exact lines we need for the map data. The tile number has to be pulled from the map. Ten tiles have to be pulled per line and 5 lines to complete the screen. The tile is pulled up and broken down into its characters and put the characters on the screen.
Getting tiles on the screen
The loop is then set and then load the variables used by TileDraw. It draws 10 tiles across, 5 lines down, and loads the variables used by TileDraw. The variable DRAWTILE_SCREEN is used to tell when we are drawing to the visible screen and writing to color ram. If set to 0, it will draw to the backbuffer. When the screen scrolls it flips back and forth between 2 screens. So Siggy had to actually work out which screen we are looking at before he can even draw it. The variable DRAWTILE_Y is in tile coordinates in the range of 0-9 for the Y area. This draws from right to left.
The @lineLoop routine, we are drawing the line of 10 tiles. The Y loop is the line that goes through that 5 times over. The X loop is drawing the tiles. Next (ZEROPAGE_POINTER_4), y is offset by Y, which is the pointer to the map tile using indirect indexed addressing. After this it is stored in DRAWTILE_NUM, which is the number of the tile to draw. Then the draw routine is executed with jsr DrawTile.
- lda #1
- sta DRAWTILE_SCREEN ; Draw to visible screen, and write to color ram
- lda #0
- sta DRAWTILE_Y ; Starting at screen tile position 9,0
- tax ; x will be the line counter used as the counter X loop
- lda MAP_TILE_POS_X ; Map X offset of tile to fetch
- adc #9 ; add 9 for right and left dra w(one time cost)
- sta PARAM1 ; add 9 for right to left draw (one time cost)
- sta PARAM1
- ;———————————————————————————-
- ; Draw the line of 5 tiles
- @lineLoop
- ldy PARAM1 ; reset Y to END of line (9)
- lda #9
- sta DRAWTILE_X ; Drawing right to left across screen
- @xloop
- lda (ZEROPAGE_POINTER_4), y ; fetch the tile from the map
- sta DRAWTILE_NUM ; The number of the tile to draw
- jsr DrawTile
- ; an optimization worth noting
- ; by using the variable and counting backwards
- ; with test for negative you can run the loopo
- ; off the variable for 8 cycles
- dec DRAWTILE_X
- bmi @newLine
- dey ; decrement the index to fetch from the map line
- jmp @xloop
Drawing the 4 x 4 tiles
The code below draws 4 x4 tiles on the screen at locations X and Y on the front screen or buffer screen. Siggy affirms, “If you are going to use the stack you always have to keep in mind, the data you put on last, is the data you are going to pull off first. It’s like a stack of cards.” You don’t want to leave stuff on the stack and always don’t want to forget we used the stack. The reason you have to be careful is because the system uses it. The only way to get things on and off the stack is through the accumulator.
Load up the variable DRAWTILE_Y and multiply it by 4. This gives the offset of the screen. This is then saved in the x register. The x register contains the line that is being looked up in COLOR_LINE_OFFSET_TABLE_LO and COLOR_LINE_OFFSET_TABLE_HI. This gives a pointer to that memory in color ram.
Flipping between screens
When the actual game is running, the front screen can be one of two screens. An example used is if we are scrolling around and doing stuff, those screens are flipping back and forth every 8 pixels of scrolling. Not on every frame though. As we go across 8 pixels and we need to make that carriage jump, we are using the back buffer to bring forward the new position. We have to work on which screen we are on since there is no way to know using the VIC chip which screen is active. At any given time that could be in the back.
- ; Draws tile 4 x 4 on screen
- ; at location X/Y on front screen or buffer screen
- ; Saves and restores A, X, Y
- ; Modifies ZEROPAGE_POINTER_1 – Screen pointer
- ; ZEROPAGE_POINTER_2 – Color ram pointer
- ; ZEROPAGE_POINTER_3 – Tile pointer
- ; PARAM5 Scratch variable
- ; Uses variables
- ; X and Y positions are given in ‘Tile Coords’
- ; DRAWTILE_X X – screen position (0-9)
- ; DRAWTILE_Y Y – screen position (0-4)
- ;
- ; DRAWTILE_SCREEN – Front (1) or Buffer (0) – Front activates color ram
- ; DRAWTILE_NUM – Number of tile to draw
- DrawTile
- ; Save our register states on the stack
- pha ; Save A
- txa
- pha ; Save X
- tya
- pha ; Save Y
- ; First fetch the Y coord to draw to and set up the address to draw to
- ; in COLOR_MEM in case we need it (a draw to front screen), 90% of the
- ; time this will be the case
- lda DRAWTILE_Y
- asl
- asl
- tax
- lda COLOR_LINE_OFFSET_TABLE_LO, x ; store base line address in
- sta ZEROPAGE_POINTER_2 ; ZEROPAGE_POINTER_2 for color ram
- lda COLOR_LINE_OFFSET_TABLE_HI, x
- sta ZEROPAGE_POINTER_2 + 1
Drawing to the Front Screen
To do this we load up the DRAWTILE_SCREEN. If we are not on the back buffer then we load up the CURRET_SCREEN + 1 (high byte). That means we are instead on the front screen. This is the pointer to the actual screen that the system is drawing to at that very moment.
- ;————————————————————————
- ; Determine the address of the screen we are drawing to and fetch it’s
- ; address. DRAWTILE_SCREEN = 0 for front screen or 1 for back buffer
- ;
- lda DRAWTILE_SCREEN ; if DRAWTILE_SCREEN = 0
- beq @getBackScreen ; we are drawing to the back buffer
- lda CURRENT_SCREEN + 1 ; Get MSB for current front screen
- jmp @getLine
Drawing to the Back Screen
If we are drawing to the back screen, we go to CURRENT_BUFFER + 1. Then in getLine, we are working out the actual screen we are using (Screen 0 or Screen 1). The actual address of Screen 1 in the VIC Bank is $4000. Screen 2 is found at $4400. If we look at the address of the screen we are drawing, it will tell us what the actual physical memory location for that screen is. It just has to be compared to $44.
If it is not equal to $44 then we are on screen 1. Now we are getting the pointer contained in SCREEN1_LINE_OFFSET_TABLE_LO and SCREEN1_LINE_OFFSET_TABLE_HI. This is getting the relevant pointer we have to draw to so that we are actually seeing what we are drawing.
- @getBackScreen
- lda CURRENT_BUFFER + 1 ; Get MSB for the current buffer
- ;———————————————————————-
- ; A = most significant byte of the screen we want to draw to, from there
- ; we need to work out what actual screens that is so we use the correct
- ; screen offset table, and from there the address of the correct line
- @getLine
- cmp #$44 ; Screen 0 = $4000 – Screen 1 = $4400
- beq @screen2
- lda SCREEN1_LINE_OFFSET_TABLE_LO, x ; fetch line on screen 0
- sta ZEROPAGE_POINTER_1
- lda SCREEN1_LINE_OFFSET_TABLE_HI, x
- sta ZEROPAGE_POINTER_1 + 1
- jmp @fetchTile
- @screen2
- brk
- lda SCREEN2_LINE_OFFSET_TABLE_LO, x ; fetch line on screen 1
- sta ZEROPAGE_POINTER_1
- lda SCREEN2_LINE_OFFSET_TABLE_HI, x
- sta ZEROPAGE_POINTER_1 + 1
Now we have arrived at fetchTile. We need to fetch the number of the tile we are going to draw. Next we load in TILE_DATA_OFFSET_LO and TILE_DATA_OFFSET_HI. The tables point the program to the correct place in the tile data, which is always 16 bytes wide. We have the tile data pointing to the line so now we know how to fetch that data.
- @fetchTile
- ldx DRAWTILE_NUM ; number of the tile
- lda TILE_DATA_OFFSET_LO, x ; Address of the tile in TILE_MEM
- sta ZEROPAGE_POINTER_3 ; is in ZEROPAGE_POINTER_3
- lda TILE_DATA_OFFSET_HI, x
- sta ZEROPAGE_POINTER_3 + 1
Then we use ZEROPAGE_POINTER_3 to get the tile address and we can now copy it. First it is appointed to the screen by the X value using DRAWTILE_X. It is then multiplied by 4 and stored in PARAM5. That is then added to ZEROPAGE_POINTER_1 so its pointed to the address in memory we want to draw to. The same is done for ZEROPAGE_POINTER_2. Now color memory is pointing to exactly to the start of the memory we want to point to. This is the very first character of that tile.
- ;—————————————————————–
- ; ZEROPAGE_POINTER_3 has the address of the tile we want- we should
- ; be good to copy now
- ; First adjust the pointer to the screen by the X value.
- lda DRAWTILE_X
- asl
- asl
- sta PARAM5
- clc
- lda ZEROPAGE_POINTER_1 ; Add X coord to the base line address
- adc PARAM5
- sta ZEROPAGE_POINTER_1
- lda ZEROPAGE_POINTER_1 + 1
- adc #0
- sta ZEROPAGE_POIINTER_1 + 1
- clc ; do the same for color ram
- lda ZEROPAGE_POINTER_2
- adc PARAM5
- sta ZEROPAGE_POINTER_2
- lda ZEROPAGE_POINTER_2 + 1
- adc #0
- sta ZEROPAGE_POINTER_2 + 1
Now we create a loop to go 4 x 4 to draw the tiles. Here we take the character from the tile data and store it to the screen. Then we are going to store the character. It is stored in PARAM5 since Siggy says the program will need it again at this point.
Then we load the DRAWTILE_SCREEN. This is because we only want to update color data if we are drawing to the front screen. If it is zero, we bypass the code that shows lda DRAWTILE_Y since we are not drawing to the front of the screen, which then does a branch equal to ColorDone.
- ldx #0 ; outer loop – lines
- @newline
- ldy #0 ; inner loop – lines
- @tileloop
- lda (ZEROPAGE_POINTER_3), y ; take character from the Tile data
- sta (ZEROPAGE_POINTER_1), y ; draw it to the screen
- sta PARAM5 ; store character for color check
- lda DRAWTILE_SCREEN ; Only update color ram if drawing to front screen
- cmp #0 ; DRAWTILE_SCREEN = 0, we are drawing to the buffer
- beq @colorDone
- ; Special Case:
- ; if screen tile Y pos is 5 AND it’s the last line
- ; of the tile, don’t color as this is hidden and
- ; color will bleed to the scoreboard
Checking for Color Bleed
Now we are checking for color bleed. Siggy says, “If we draw that tile and we draw the color onto the screen, we are only showing 3 characters of that tile. It’s going to write all of them. The color is drawn in there down to the scoreboard screen. Screens adjust the character data. The color ram is really an overlay of the screen you are looking at. It’s separate to itself. It goes from the top of the visible screen to the bottom of the visible screen. It has no concern if you are swapping things in or out.
Therefore this special check had to be conducted. We load up DRAWTILE_Y. Then we check to see if we are on tile position 4, which is the bottom of the scrolling screen. If DRAWTILE_SCREEN is 0, 1, 2 or 3 then the color is drawed in.
If not then we compare X to see if it’s a 3 then we skip out and the color is not written in. This covers up the shaky raster line.
- lda DRAWTILE_Y
- cmp #4
- bne @doColor
- cpx #3
- beq @colorDone
- @doColor
- In doColor, we are resetting the stuff that was saved earlier in tileloop.
- ; Need to do some register juggling for color
- lda PARAM5 ; load current character to A
- stx PARAM5 ; store X (current line number)
- tax ; put character number in X
- ; We can access the color this way because the
- ; character color data is always in the same place
- ; and it’s only ever going to be offset by 0-255
- ; so we can use Absolute Indexed Addressing
Next we are fetching the color in TILE_COLOR_MEM and write it back to color ram at the correct X position. Then in colorDone, the Y register is increment and we go along and if we come to the end of the line we go to tileloop. If we are at the end of the line we are drawing, we will be done.
- lda TILE_COLOR_MEM, x ; fetch the color for this character
- sta (ZEROPAGE_POINTER_2), y ; write it to color ram at correct x pos
- ldx PARAM5 ; restore X to it’s original value
- @colorDone
- iny
- cpy #4
- bne @tileloop
- inx
- cpx #4
- beq @exit
The next segment of code is incrementing the pointers. We are adding 4 to look up the next tile on the tile data. We are adding 40 for our screen and our color ram. Once the tiles are completely drawn, then the registers are restored to their original state.
- clc ; Next line
- lda ZEROPAGE_POINTER_3 ; Increment to the next line in the title
- adc #4
- sta ZEROPAGE_POINTER_3
- lda ZEROPAGE_POINTER_3 + 1 ; may encounter a new page
- adc #0
- sta ZEROPAGE_POINTER_3 + 1
- clc ; increment to the next line on the screen
- lda ZEROPAGE_POINTER_3 + 1
- adc #40
- sta ZEROPAGE_POINTER_1
After we return from DrawTile, we are back in the routine lineLoop to continue execution of the next lines. We are in a loop here. We draw one tile and have to look up the next tile. Next we are using DRAWTILE_X to count backwards and test for a negative and branch to newline if less than zero. If we are not done though, we jump back to the xloop and repeat. Once this is completed we do another line. This is very similar in retrospect and repeats until the loop is finished.
- dec DRAWTILE_X
- bmi @newLine
- dey ; decrement the index to fetch from the map line
- jmp @xloop