Introduction
The Commodore 64 was a 6502-based computer. That chip just had 3 8-bit registers, each with slightly different purposes and capabilities. Working with assembler to program the computer to play games was the best job in the world. It probably still is.
Assembler
Just before I get into the various uses I made of the C64 character mode, I would just like to mention the dark art of programming in assembler. Way back in 1984 I was just using a pair of C64s. I would be assemblin the program on one using the Commodore macro assembler and a 1541 disk drive. The process took about half an hour to assemble and construct the binary executable for a game, being anything up to about 16K. While the assembly process was going on I would have to wait and hope that the assembly would be successful. That taught me to type carefully and read everything back to achieve that first-time success.
While the assembly was going on I needed something else to do, so I had a second C64, also with its own 1541 disk drive, to work on the graphics. I used the SpriteMagic and UltraFont editors which were bought as a pair from the local computer shop. Every town had one back then.
Once assembly was complete I could download the executable to the C64, along with the various graphics files and fire up the program. We had no debuggers, let alone remote debugging from another computer, so if the program crashed, and it would... a lot then you're left with nothing. There were no magic cartridges back then either, which might have offered a bit of disassembly of the crash site. I did buy one later, mainly to try to thwart them from being able to steal our programs, but that`s another story.
6502 assembler is a pretty simplistic affair by today`s CPU standards. You have 3 usable registers, each with slightly different capabilities, so you're writing in simple blocks of code to carry out slightly less simple operations. When it goes wrong it might just not quite do what you want, or it might go into an endless loop, or it might go rogue and start executing where it shouldn`t. When you have user graphics on the screen rather than letters, as we often did, then the computer has no way of showing you a message as to what is wrong, and in any case you wouldn't have space for many messages of any value.
You tend to write in the same patterns all the time in assembler. You have to figure out how to write your new code a bit at a time. If you write a giant routine in one go and then run it straight off it`s just bound to end in disappointment. The clever architect figures out whether write inner loops first and test them, or write the outer loop first and maybe use the trusty method of changing the border colour to show different results. We used to set the border colour to differnt colours between each major routine call so that if the program crashed then the border colour would indicate the culprit.
I would have a pad of squared paper by the computer. Every test cycle I would write down every bug I found and any tuning speeds or rates that I felt needed adjusting. I 'd try to test as thoroughly as I could, given that at any one time there would likely be a page full of things to check from the last time. The program might crash and that would bring things to a halt. I would then reload and try to figure out what caused the crash, and then avoid that and do any other testing. I could tick off items on the list and start creating a new list. Usually I would be developing the game and the titles sequence. I would then try to fix all of the bugs and change the tunings as required before embarking on another 30 minute assemble.
We didn`t have conditional assembly as such, or at least we weren`t using it because we needed to keep the source code files as small as possible. I would therefore have code like setting the start level to any desired test level and would have to remember to remove all that at the end. There are no cheat modes in any of my games. I felt that might distort the testing and my view of how easy or tough the game was. Being able to start on any specific level though is a time-saver when getting to a particular feature or issue. I also tended to just make the level I wanted to get to the first one, which is turning everything on its head.
When I was developing game levels, rather than try to develop a tougher or easier level as required, I would just develop a level and then work out where it would go in the hierarchy. Whilst you can make a level a bit tougher or easier with tweaks, to alter the difficulty a lot might take a whole bunch of time.
While we never had to decide on an end date at the beginning of a project, we certainly were getting the idea that every game was taking a bit longer than the previous as the program was getting slightly more clever and bigger than the last. Based on that we would have to start working to an approximate end-date once we thought we were in the last 6 weeks or so. We were starting to fill the machine and it was important to ensure that the game was bug-free. Fortunately we didn`t have any tough issues to fix at the last minute, though changing the Paradroid firing mechanism for the third time was quite hair-raising.
As the last weeks approach; schedules start to be made for production and delivery of the game to wholesale. We would also visit some of the magazines, and have a launch lunch in London for the magazines there. We would want the game to be complete before showing it off. I wasn`t totally happy about giving a complete game away for review because it was unlikely that the game would be fully seen in the time needed to get a review to print. However they did insist on having the whole game. When we were there to talk to the press we could answer questions as well as demonstrate how to play the game. That used to work out well for us as the reviewers didn`t get frustrated when they couldn`t figure something out.
ABMON
One of the first things I did write was a little monitor routine. I reserved 16 characters in the character set for the numbers 0 to 9 and A to F so I could display hexadecimal. I had it operate every game frame, selectively on a button press, to display an address in memory plus the value at that address. I just had a simple cursor mechanism rigged to flash one character and I could dial the digit up or down with cursor keys. Usually it would be on the top line showing something like:
0035 01
meaning that the byte at address 0035 was set to 01. I could alter the value up and down as well as the address; so if a variable got set incorrectly I could change it. Mostly it was a good idea to pause the game while using this.
I had a master sheet of all of the variables with their addresses written down, and as they don`t move then you get to memorise the important ones pretty quickly. I could dial in the address of a particular variable and see what it was set to.
I might have rigged it to skip over the chip registers as you don`t want to be writing to some of those with out-of-date values.
ABMon allowed me to get a glimpse into the workings of my programs and was invaluable in solving some of the bugs.
As a game was just about to complete, i.e. no more development was needed but there might be a bit of tuning tweaking still to be done, I would remove all the debug code, including ABMON, in order to save some space, maybe for a couple of extra graphics. I would also try to use the start-up code memory space for something else in order to reuse the code that has done its job and serves no purpose other than to reveal how I initialise my code. I might use the space as a buffer for some process or other, maybe the object variables.
Zero Page
The first 256 bytes in the computer have an address that starts with 00, hence the first 256 bytes are referred to as the zero-page. 6502 assembler has short instructions that can read and write to these 256 bytes nice and quickly. Naturally the operating system is using these bytes as variables so when you take over the machine you will likely just put your own values there and there is no way of getting back to the OS other than flipping the power button. Amazingly though you`re back to BASIC in a second with a fully operational computer.
I kept all the main variables in the zero page. It still surprises me to see cheat pokes where the number of lives, for example, is not stored in zero page.
I didn`t have space in zero-page for all of the objects` variables such as position and animation for 16 objects, so I used to keep them in a table elsewhere, but I found it economical to copy the variables for one object into zero-page at a time, operate on the object, then when finished I would copy the bytes back out. That made the routines a bit smaller and faster. It certainly saved space and probably some execution time.
Around the time of Alleykat, late `86 or early `87, we got our mighty PCs. These didn`t have hard-drives, nor Windows, it was just loading DOS from a floppy drive into RAM, the machines had one Megabyte! We bought in some cross-assemblers and the EC editor. EC allowed us to edit the source code and had enough memory in the machine to allow us to put comments in the code. When assembling on the C64 we didn`t dare spend any space on comments as the assembler and the code had to sit in memory at the same time.
We then had to write the software at the C64 (and Spectrum) end to receive the software downloaded from the PC through the Centronics parallel printer port. That meant that we only had to load that downloader software from the 1541 drive and then it would be primed to read the data sent by the PC. Actually the graphics data would also have to be loaded from the C64 drive as they were still being produced on the C64. Our mighty PCs had just an amber screen and no graphics packages. Still, they were more like what I had been used to in my previous job working on an IBM mainframe.
PDS
As I got to writing Intensity, a PDS development kit became available. This ran on my PC and allowed me to edit 8 files at once. That was rather handy as this was to be by far the biggest program I would write on the C64. I ended up with a 29K executable, almost half the available memory. There were landscape analysis buffers for height maps and recommended altitude maps, all requiring code to generate them. I also managed to squeeze 75 levels or so of screen maps in there, and used the RAM under the ROMs as video memory for the first time. That seemed to offer me the best available space and I could switch all of the ROMs off and keep my 29K of code in one contiguous lump.
Video RAM
You had to consider how you`re going to load your code and data into RAM in one load operation, take over the machine and get yourself a clean 16K of video memory, since the sprites, screen and character sets have to come out of one quarter of the memory. Of course you can swap in alternate character sets from level to level. I had 8 different 2K sets for Alleykat for the different race styles. Usually all of the game graphics at any one time would have to come from one 2K set of 256 characters. We might use a screen interrupt to swap character sets for an information panel. Typically then that would be 4K or 1/4 of your video RAM. The screen would take 1K for a character map, leaving 11K for sprites, at 64 bytes each that would be 176 different sprites. Some games may well have paged in different images per level, or even in real-time, but I don`t believe I went that far. Equally we didn't think of compressing anything as it would have meant spending space on a decompressor algorithm.
Optimisations
To get extra things done in the time you have to think of cunning ways of doing less than you thought to achieve what you want. Set up tables of common results, such as multiplying numbers by 40 so you can look up answers rather than calculate them.
By Morpheus I had a sine and cosine table coded in hex so that I could do circular effects. There was a multiply or two in there for sure, too,many different inputs for a look-up table. You only need to set up the sine and cosine once in the life of a fragment, after that it continues on using the calculated sine and cosine speeds, so I was storing Cartesian speeds and polar (circular) speeds and directions.
Conclusion
Developing in assembler with hardly any debugging tools, certainly nothing modern, was not for the faint-hearted. When things go wrong you have to go back to the source code and spot your mistake. Maybe a loop went on too long? Did you choose the right register? There were only 3!
It is very satisfying seeing something you`ve created working, and you know that it`s running as fast as it can.
0 nhận xét:
Đăng nhận xét