Smooth Scrolling Commodore 64 Maps

In this section you are going to learn how to use CharPad to save maps, pixel scroll your maps, raster routines work, copy maps to front screen and back buffer, and how to add color scrolling.

CharPad Spelunker Map

T

he video series launched off with my presentation of the map designs I created for our Spelunker clone. I used CharPad 1.8 to design 4×4 map tiles. To save on being redundant with the graphics, I reused a lot of the same character set tiling. The game itself will be using CharPad when it is complete. CharPad is a tool that allows you to build a game map. It contains a display for the Map Editor and the Tile Set. In the Tiles environment, in has a Tile Editor, Tile Set, and Character Set. The Tile Editor is the area used to draw the tiles individually with a close up view. It also allows you to control the multi colors here.  The Tile Set shows the complete tile layered next to the other tiles created. The total Tile count and Tile size can be altered here to increase the tiles available. Finally the Character Set are shows all of the character set tiles you have created in a much smaller area. There is a section that can be used to compress the tiles to save for maximum memory requirements.

After you have constructed your tiles and map, you can save them to your computer. First though it is a good idea to save the project. Just click on the File menu and select Save Project As, select a directory on your system, and in the File name choose a project name. Then click on Save and it will be recorded with the extension of .ctm. This saves all of the settings and allows you to load up the tiles and map.

The next thing is to save the map in a format recognized by CBM Prg Studio. Click on the File menu again and select Export Data. A small window shows up. Under the category Export items, by default it will create a Character Set, Tile Set, Map, and Attribute Table, but gives you the ability to remove an export by unchecking the box. The Attribute Table is used to track the area you want to save for the attributes. The selections are Per Tile, Per Tile Cell, and Per Character. Finally the category for File Format is used to save your work as Raw Data or in PRG Format. The Raw Data will be used for our project. Once the OK button is clicked, windows will populate one after the other requesting the file names for the Character Set, Tile Set, Map and Attribute Table.

Spelunker Clone Character Map

Soon Siggy took over the discussion again. He said that our project ever map will have its own character set, which takes up 2k (two thousand  bytes) of memory. Running some tests through Charpad, Siggy discovered that every level can have its own character set. With 4 by 4 tiles, every map had 50 tiles, and every screen was 50 x 50. The screen would be under 4k. That allows expansion for more tiles and map size limits. He stated that the 4 x 4 tiles, would span 5 screens across. The export into assembly code provides a lot of information needed to decrypt the map.

“It is important to have a test map that will work”, he stated. Once we get the character sets and maps loaded the next logical step will be determining how they will be laid out. Siggy stated that he needs a “couple of characters that he can rely on that are not used for anything else. Rather these will be changed through the program to be used for different things. With the graphic slope gradients that are in the same character numbers, he can make a table to decode the sprite’s delta.  This is the sprite’s position within the character. A table can calculate where the Y position is located to manage the game collisions. This would be enabled to allow a sprite to walk up and down slopes smoothly.

In the original Spelunker there were elevators, ropes, and other objects. Keeping these objects in the same place in the character set, he stated “We can change the look without having to change the code that drives them.”

I mentioned that I wouldn’t want to use the same character set as the original Spelunker game. Siggy agreed that we can make it better. However, it is essential to have “something to work with”.  At this time, he was also working on the scrolling during our two week break.  Stated he had the scrolling working in the X direction for the screen push, but then suddenly his project got corrupted. He is very adamant about saving his work to his Google drive. He started a “basic scroll” with the limited time he had after having to reproduce his work. He used three different screens to create the scrolling effect.

Scrolling the Screen in pixels

Demonstrating the basics of scrolling, Siggy presented his work in the live stream. It was scrolling the screen to the right. The same Berzerk (Kill Bots) occupied the screen again. One was stationary and the other moved back and forth along the bottom. He stated that the flicker seen in frame 16:27 is because he didn’t do the color copy correctly.  He just threw in some bytes to copy the color data. There is also a glitch at the bottom that will need to be ironed out later. “This is a load time scroll”, Siggy mentioned, “that is not going as fast as it should”.  A delay was implemented for bugging purposes, which is his timing method.

The UpdateScroll routine explained

A timer is loaded to mask the lower two bits. These will provide a decimal number from 1-3.  Bit zero provides a pulse rather than a sustained delay timer. It works like a ticker.

  • UpdateScroll
  • lda TIMER ; temp timer for debugging
  • and#%001 ; actually scroll every 3rd frame
  • bne @cont

He pointed out the reason for the flashing sprites is an update to a back buffer screen. The sprite pointers are linked to each screen being displayed. “If I change the screen then he has to change the sprite pointers on the screen too to match up”.

Returning to the topic of the Spelunker game disassembly, Siggy said he “noticed there were big gaps in the character set”. One of the character sets would overlap on another one, but he wasn’t sure of the reason for this.

The Screens Explained

Spelunker Screen 0His implementation of the game to date consisted of three screens. The top part where the sprites were moving around at is the “main screen”. A raster interrupts changes it to read screen zero at the very top part. The green raster lines shows where the scoreboard would be at. “Behind screen zero is our back buffer”, he mentioned, which is on screen one.  So if two character sets were loaded in one can occupy the top section and one will manage the score board. That brings you to screen 6 by the time the program moves through memory.


Understanding Raster Routine Screens

From the top to bottom, the raster screen moves down to display the top part, which is screen 0. It is 38 characters wide. “The part protruding out from the bottom is contained in screen 6”, Siggy stated. It is 23-24 characters in height. The screen shrink from the raster routine has created the strange line at the bottom.  He plans to cover it up though with sprites.

The control panel contains its own screen with its own character set. The top part is character set 1 and the control panel is character set 2. The top black edges of the screen is the first raster interrupt routine. It points to the screen being displayed in that area. “It’s double buffering, but not in every frame”, he added. The green raster routine is displayed another screen. When the raster scan reaches the bottom, it is turned off, reset, and returns to the top part to start over again. The page flipping and character set is done in the black area.  The yellow raster routine contains the section where the program starts in real time.  A temporary red raster flash is displayed when the back buffer is being shown.

The hardware scroll is also adjusted from 0-7 pixels. It scrolls across the screen. Before it arrives at pixel 7, the foreground screen (that is visible) copies to the background screen one character across. This copying is done so the program can be timed out and complete all the work in 1 frame. Unfortunately this also contributed to the annoying glitch that was seen on the screen. “Because we are copying to a screen that is not seen, we can spread that work across from 0-7 pixels that is scrolling”, Siggy stated.

When the scrolling arrives at zero, the screens are swapped so that the character jumper set is now displayed, but the character ram still has to be copied. The color ram is being updated as it is being displayed, which triggered the line glitch. Within scrolling and timing there really isn’t enough time to scroll an entire screen without a lot of tricks. “At some point you are going to be displaying while you draw”, he advised, which can create problems.

For a possible future episode, he wants to share the load with the character copying and set it up in a particular way. “When we first started you notice it looked fairly centered”, he mentioned. This is because the routine started on pixel 3 which is right in the center of the character. This allows free movement to the left or right. The character data is copied across. To get around the issue with the color ram, he wants to set it up so that it copies the color ram from the middle of the screen down towards the scoreboard. This is so it doesn’t interfere with the main program. At the top the color ram isn’t copied yet. He wants to time a switch for the frame before it is supposed to be copied. This occurs on frame 7. When it scrolls to the right, zero is a flush character. The next character that jumps across is a cross and scroll back seven. A jump is executed on frame 7. He plans to copy in the color ram down to the middle once the raster beam has passed. Then drawing can be done since the raster beam hasn’t shown anything yet.

Further Scroll Code Details

Siggy revisited the scroll code again after some time. You can watch this continuation at 33:46 in the video. The next line decrements the variable SCROLL_COUNT for the scroll right routine. Then we are masking out the bits to search between 0-7. This keeps in within the range of 0-7. Here it is masking down the bottom 3 bits. All the other bits are ignored, since they are not needed. It uses 0-7 since there are 8 bits per character, starting at 0 and ending at 7 for each row.

After this SCROLL_COUNT is stored. Finally we check if the scroll has counted down to zero and then copy the character data.  The character data is where Siggy drew the bulk copy of the entire screens of the characters. The ColorScroll routine is where the color ram exists for copying. When we reach frame 7,  the color is copied across, and the screens are swapped.

  • dec SCROLL_COUNT
  • lda SCROLL_COUNT
  • and #%00000111 ; mask it 0-7
  • sta SCROLL_COUNT
  • cmp #0
  • bne @frame7
  • jsr CopyCharData
  • rts
  • @frame7
  • cmp #7
  • bne @done
  • jsr ColorScroll
  • jsr SwapScreens
  • rts

Swapping the Screens

The code below keeps track of which screen the player in on (either SCREEN 1 or SCREEN 2). The most significant bit of a SCREEN + 1 will be either $40 or $44. For example, $44 is screen 2. If the game is on SCREEN 1 then it will be changed to SCREEN 2. Vice versa, if the player is on SCREEN 2 then change it to SCREEN 1.  This allows it to flip back and forth.

  • SwapScreens
  • lda CURRENT_SCREEN + 1
  • cmp #$44
  • beq @screen2
  • lda #<SCREEN2_MEM
  • sta CURRENT_SCREEN
  • lda #>SCREEN2_MEM
  • sta CURRENT_SCREEN + 1
  • @screen2
  • lda #<SCREEN1_MEM
  • sta CURRENT_SCREEN
  • lda #>SCREEN1_MEM
  • sta CURRENT_SCREEN + 1
  • rts

Copy Screen to Backbuffer

The routine for CopyCharData is used to copy the screen to a back buffer in memory. Siggy says “We have to unroll the screens”.  The fastest way to do this is to use the immediate addressing mode. With the direct memory address it can’t be put into a variable. The code below scans each screen line individually and copies from SCREEN1_MEM to SCREEN2_MEM. There is another copy routine in CopyFmScreen2. This one copies the back screen (that is in front), copying here from SCREEN2 to SCREEN1.

  • CopyFmScreen1ldx #0@copyloop
  • lda SCREEN1_MEM + 1, x
  • sta SCREEN2_MEM, x
  • lda SCREEN1_MEM + 41, x
  • sta SCREEN2_MEM + 40, x
  • lda SCREEN1_MEM + 81, x
  • sta SCREEN2_MEM + 80, x
  • lda SCREEN1_MEM + 121, x
  • sta SCREEN2_MEM + 120, x
  • ; …. and so on

To elaborate on this further, Siggy moved over to the raster.asm file. First we check what the CURRENT_SCREEN is. If it is SCREEN2 then we go to @backscreen.  Next we set bank 1 for memory control (memory address $d018).

  • lda CURRENT_SCREEN + 1
  • cmp #$44
  • beq @backscreen
  • lda #%00000010 ; screen 0 charset 1
  • sta VIC_MEMORY_CONTROL
  • jmp @cont

In the next part, the Backscreen routine first sets up which screen is being displayed.

  • Backscreen
  • lda #%00010010
  • sta VIC_MEMORY_CONTROL
  • cont

Now we get the VIC_SCREEN_CONTROL_X ($d016) and mask out the bits. Next we ora (set) the SCROLL_COUNT in. This will get the SCROLL_COUNT from 0-7.  Then it will place them into those bits. This is what is going to scroll the screen across from 0-7 pixels.

  • lda VIC_SCREEN_CONTROL_X ; take the current values (from scoreboard)
  • and #%11110000 ; mask out the scroll value and bit 3
  • ora SCROLL_COUNT ; or in the scroll value for bits 0-2
  • sta VIC_SCREEN_CONTROL_X ; save it in the register
  • jsr UpdateTimers ; update our timers and automatic system
  • jsr ReadJoyStick
  • jsr JoyButton

Siggy then added, “When we get to the bottom of the screen we change it to the default settings (no scroll, no multi-color, and 40 columns). Then we change it to character set 2 for screen 6.

  • IrqScoreBoard
  • Lda $d019
  • Sta $d019
  • Lda #<IrqTopScreen
  • Sta $0314
  • Lda #>IrqTopScreen
  • Sta $0315
  • Lda #$09
  • Sta $d012
  • Lda #COLOR_GREEN
  • Sta VIC_BORDER_COLOR
  • Lda #%11001000 ; default settings, no scroll, multicolor, and set to 40 columns
  • Sta VIC_SCREEN_CONTROL_X
  • Lda #%01100100 ; Character set 2, screen 6
  • Sta VIC_MEMORY_CONTROL
  • Jmp $ea71

The ColorScroll routine is literally the same type of copying process. However, it doesn’t copy to anything, but just itself. Essentially it copies from one character to the previous character position behind it. This prevents a lot of tearing of the screen.

  • ColorScroll
  • ldx #0
  • @copyloop
  • lda COLOR_MEM + 1, x
  • sta COLOR_MEM, x
  • lda COLOR_MEM + 41, x
  • sta COLOR_MEM + 40, x
  • lda COLOR_MEM + 81, x
  • ta COLOR_MEM + 80, x
  • lda COLOR_MEM + 121, x
  • sta COLOR_MEM + 120, x
  • lda COLOR_MEM + 161, x
  • sta COLOR_MEM + 160, x