When I was a kid, I loved the arcade game Moon Patrol. If you’re not familiar with it, you were a driver of a lunar vehicle armed with guns that shot forward and up. Because you were on the moon, the lower gravity gave you the ability to make the vehicle jump over obstacles, such as rocks, and holes in the terrain. While you’re navigating the landscape, aliens show up to shoot at you. You, naturally, shoot back. One of the cool visual features of the game was the moving landscape. The scrolling was smooth and the programmers created three levels of scrolling for a visual parallax that made the mountains and building look far off in the distance. The other cool thing was when you blew up, your tires flew in all directions and bounced around for a while.
We’re not here to talk about bouncing tires though. When I was a kid, I wanted to write my own Moon Patrol for the C64. The thing I absolutely had to make was at least a single layer of scrolling land to drive on. I figured the multiple levels would be too difficult.
So after many attempts, I came up with something like the code below:
(download all the samples from this post)
I made a terrain with Commodore character graphics at least 40 characters wide, then doubled it by adding it to itself. This became the basis for the scrolling. It then became a simple matter of running through a loop and printing a 40 character section of the terrain over and over again while increasing the starting point by one until I hit 40 and then start over. Running the program made me a happy kid. It looked good! It still moved in 8 pixel blocks, but it was an infinite scrolling terrain.
Make It Smooth!
Feeling pretty good about my little program, I decided I wanted to try and make it scroll smoothly. I pulled out the handy Commodore 64 Programmer’s Reference Guide. On page 128, it has some instructions on how to do it. Follow these steps:
- Shrink the screen
- Set the scrolling register to maximum (or minimum depending on the direction you are going to scroll)
- Place new data on the proper (covered) portion of the screen.
- Increment (or decrement) the scrolling register until it reaches the max (or min) value.
- At this point, use your machine language routine to shift the entire screen one entire character in the direction of the scroll.
- Go to step 2.
It all looked good, except for that step 5. I didn’t know any machine language. Still, I wasn’t moving the entire screen, just one line. Maybe I could do it in BASIC.
So the register for scrolling on the X axis is 53270. Like many of the registers in the C64, the location does multiple things. Here’s the breakdown:
- Bits 0-2: Fine scroll display horizontal
- Bit 3: Select a 38 or 40 column display (1=40, 0=38)
- Bit 4: Enable multicolor text or multicolor bitmap mode (1=multicolor on, 0=multicolor off)
- Bit 5: Video Chip reset (0=normal operation, 1=video completely off)
- Bits 6-7: Unused
I followed the directions in the guide to set the screen size and nested a loop inside the print loop to change the X position. The manual talks about using logic to filter out bits you’re not working. I realized that for the correct display, I need everything at zero except for bits 0-3. That made things easier for me to understand. I just needed to poke 0 through 7 into the register to change the position of the screen.
My program probably looked something like this:
The only things added were the nested loop for moving the screen using POKE53270,I at line 44. Unfortunately, BASIC proved it wasn’t up to task. If you run the program, you’ll see its kind of jittery. There is some smooth movement, but it skips back every 8 steps. The reason for this is that the computer has to do 2 things at once. 1) update the terrain, 2) reset the X position. If you update the terrain first, then the ground gets drawn back 8 pixels before it jumps forward in the next poke statement. If you reset X first, the ground jumps back 8 pixels before the new land is drawn. The result either way is a skipping look on the screen.
Back when I was a kid, this stopped me cold. I couldn’t get the smooth scrolling I wanted and decided to keep with the first version of the program and work from there. At the end of the day, the program ran too slow for what I wanted to do. All the game play had to be within the loop I had created and every added statement slowed down the whole thing.
But What About Now?
But what if I had a little more knowledge, like I do now. Could I make it work in BASIC now? The step I tried next was to revert to page swapping. This is where you display one screen while drawing a different screen in memory and then flip them when it was the correct time. This should hopefully reduce the amount of time need to move the ground the 1 character over while resetting the scrolling register. I came up with the following program.
First, we’ve added some arrays to help manage the screen flipping. S() is for where the VIC II is looking at the screen, P() is for where the C64 is outputting to the screen. They are set opposite, so when the VIC II is looking at the value for 8192, the OS is outputting text to the screen at 1024 and vice-versa.
From there, I’ve created a B$ that holds the cursor characters to place the mountains on the screen. I call it up on line 44.
From 24 to 27, I prep two screens for scrolling, the first one, located at the default (1024) and the second on at 8192. I change where the OS sees the screen while doing this, but not what the VIC II chip is looking at. POKE642,4 & POKE642,32 do that work. As mentioned in a previous post, location 642 designates where the C64 outputs text to. To find the location, simply multiply the value poked, by 256. (4 * 256 = 1024) (32 * 256 = 8192). I added the memory location to ground display so you know which page you’re looking at.
30 to 50, I had to change up the loop a little. I did this to try to get the POKE that changes what page the VIC II is looking at as close to the register that shifts the screen over 8 bits. This was done to try to avoid the skipping of the land that was happening previously. Something worth noting is that in line 44, the IF statement checks to see if we’re at location 6 in the screen shift. That’s one before the page flip. If we are at 6, the computer shifts which page it’s printing to and draws the next section of land so it is ready to flip after location 7.
This almost works, giving me a fairly smooth scroll. There are still some jerky movements here and there, so it will need a little more work and a little machine language routine added.
BASIC was simply not fast enough to even do the page flip needed for smooth scrolling. I needed to write a small ML program to do that one step in the program. It is a small enough piece of code that I included it in the BASIC program with DATA statements.
So again, a few changes from the previous program. First, we’ve added a machine code loader at the end of the program using DATA statements. We also put our run loop back to nested FOR/NEXT loops (lines 40 to 50). We also added some memory locations that hold our register information. 251 holds the page that the VIC II chip is looking at and 252 holds where we’re at on the horizontal scroll register. Finally a system call, SYS49152 was added to run our short ML routine. The ML routine looks like this in Assembly.
There’s a lot of text here, but most of it is commenting. The actual program that the computer will see starts at line 00021. Lines 00012 – 00017 set up some symbols that the assembler uses like variable when assembling the code into ML. Numbers are listed in hexadecimal format and I put the decimal equivalent in the remarks following. The hex numbers under the CODE column in lines 00021 – 00029 are the actual machine code instructions that the computer can understand. These are the numbers, when converted to decimal, that are in the DATA statements in the BASIC program above.
Breaking it Down
The ML program simply does the equivalent of 2 BASIC statements. They are listed as comments in the program at lines 00020 and 00026. Here’s a breakdown of one of those statements.
A comment to show the BASIC equivalent of we are about to do.
C000 – The memory location (in hex) this command is going to be stored. Its decimal equivalent is 49152.
LDA VMCSB – LoaD the Accumulator with the value from a memory location. The equivalent to PEEK in BASIC. In this case, the assembler is using the symbol VMCSB which was assigned earlier to $D018. The command could have also been LDA $D018. Symbols become much handier the larger your program becomes. $D018 is 53272 is decimal.
AD – This is the actual machine code (in hex) that the 65XX processor understands. AD is the OP code for this version of LDA. There are several different OP codes for the command LDA depending on the type of addressing you are doing in the command. The type we are using here is absolute. We are telling the computer to pull the value out of a very specific address.
18 D0 – The address the LDA command is acting on. As mentioned above, the LDA command is pulling the value from $D018. In ML, 16 bit numbers are stored in low byte, high byte order so $D018 becomes $18, $D0.
This command takes 3 bytes of memory to store, one for the OP code and 2 for the absolute memory location it is looking at. The next command will store at the next available memory location.
C003 – The next available memory location for code.
AND #$0E – The assembly command AND. This will take the number we loaded into the accumulator in the previous command and compare it with the number following it. Like the LDA command, AND has several different addressing methods. The method we are using here is immediate. The pound (#) sign is the indicator for immediate mode. It is telling the CPU that it should do the AND operation with the number $0E (decimal 14) specifically.
29 – The OP code for AND in immediate addressing mode.
0E – The number that will be used to perform the AND function. This command only takes two bytes of information.
C005 – The next available memory location for code.
ORA VMCDTA – The assembly command OR Accumulator. This executes an OR function with the accumulator and the info after the ORA statement. Like the other command previous, ORA has several different addressing modes. In this case, we are using Zero Page addressing. The computer is looking into an address on Zero Page (1st 256 bytes of memory) for the value. In this case, $FB or decimal 251, a location we used in the basic program to the ML program which page we are switching to. The advantage to using Zero Page is that it uses 1 byte less than absolute addressing and it is 1 cycle faster, if you are worried about such things. There is very little Zero Page memory available for users as most of the memory is used for essential tasks used by BASIC and the OS.
05 – The OP code for ORA using Zero Page addressing.
FB – The address the ORA pulls from to do its function. Notice that the command is missing the pound sign (#). If the pound sign was accidentally included in the command it would perform the OR function with the number $FB instead of looking inside the memory location $FB for information.
C007 – The next available memory location for code.
STA VMCSB – STore the Accumulator, the equivalent of POKE in BASIC. We are taking the value left in the accumulator after performing an AND and ORA commands on the value store in it and placing it in the memory location represented by VMCSB. In this case, $D018 or decimal, 53272.
8D – The OP code for STA in absolute addressing mode. Like the other commands, there are several different addressing methods you can use here. Each method has a different OP code. Again, absolute addressing means that we are putting the data in the exact address indicated.
18 D0 – The address/register we are storing the byte from the accumulator in. In this case $D018. Remember, when working with 16 bit numbers on the 65XX, it is low byte, then high byte.
Smooth at last!
So when you run this program, you should have a nice smooth horizontal scroll without any skips or hiccups. We did need to use a little machine language to make it work well. Basic was just too slow to make the hop every 8 bits. The program is still mostly BASIC though. Because of that, there are a few issues with the scrolling. First, this is as fast as it goes. Once you start adding game-play into your game, it’s going to get really slow. Also, we haven’t addressed the fact that if you put any other characters on the screen, they will be smoothly scrolling to the left and then jumping back every 7th bit. Not a great look by any means. Luckily, sprites are not affected by the scrolling register on the VIC II chip, so you could get away with making a game like this without any characters not involved with scrolling out of sight.
Next time we’ll take a look at doing smooth scrolling entirely in assembly and make a utility to make it useful in BASIC programs. It will be very similar to what I did in Lunar Lander.