"Game Size: The Forgotten Discipline"
PC Techniques, Vol. 6, no 3
Aug/Sept. 1995, page 93.
What appears here is the original manuscript, as submitted to Jeff Duntemann. Any changes in the published version are due to Jeff's expert editing.
At a recent conference, I ran into a user of a shareware game who was complaining that the game wouldn't run on his ancient 80286 computer. It was running too fast. The program, apparently, was written years earlier to run on an XT, and performed badly on anything faster. He was wondering if I had any suggestions on how to fix it. I didn't, but I took the opportunity to recall the good old days of game programming, when developers knew how to get the most out of those old, primitive systems.
The thing that sticks in my mind the most about the early days of game programming was how much attention we paid to the size of a game. These days, all the emphasis is on speed. If a game fits on ten floppy disks or a CD-ROM, it isn't a problem. We can afford to be wasteful where size is concerned, as long as we have adequate speed.
Do you remember when floppy disks were so expensive that games had to fit single 360K disk? Disk space wasn't the only problem back then. You couldn't count on a system having 640K bytes of memory, or even 512K. To reach the widest audience, a game had to run in 256K or less.
Programming to those types of minimal configurations is becoming a lost art. Game programmers who remember how it was done still have the necessary skills. We remember how to optimize for size. But you don't see much written on the subject, because it is no longer an issue like it used to be. Still, there is no reason to be sloppy. Wasted space is wasted space. Even if you don't need to optimize for size, it still pays to know how to do it. And there are still occasions where optimizing for size is important, for example when writing code for an embedded system or an EPROM.
With this in mind, I am offering the following list of suggestions for optimizing games for size.
Use C, not C++
C++ is a powerful language. While you can use it without a speed degradation, you will most likely notice an increase in the size of your program. When optimizing for size, use C, stripped down to the bare minimum. Also, compare compilers. Some compilers generate smaller code than others.
Check your compiler switches
Most compilers give you a choice between optimizing for speed and optimizing for size. Choose optimizing for size, and be sure to turn off the debugging information. Keep an eye on things like stack size and heap size, and adjust as appropriate.
Be careful with overlays
The big problem with overlays is the number of disk accesses they can generate. If these are not planned carefully, your program will access the hard disk constantly, causing your program to run very slowly. However, if used correctly, overlays can be a wonderful way to keep a game running when RAM is low.
Use the medium memory model if possible
You want your data to default to near memory. You can fit a lot of data in a 64K segment, especially if you make heavy use of globals and reuseable arrays. If Windows Write can be written in medium model, so can your game. The small model is also good, but it is usually not practical to fit both code and data into 64K segments.
Use the smallest integral type
Don't use long integers when short integers will do. Don't use short integers when bytes will do. This isn't terribly important where individual variables are concerned, but pay careful attention to your arrays.
These days, programmers are taught to avoid globals for stylistic reasons. However, they can actually be quite efficient, both in terms of speed and size. There is a time and a place for everything. If you are writing a program for a bank that 20 other programmers are going to work on, you should avoid using globals. If you are writing a game and you want to squeeze every drop of performance out of it, use them liberally.
Use malloc() and free()
This is obvious. When an object, such as a bitmap or a sound effect, is no longer in use, its space should be available for some other object to use. A variation on this is to allocate all the free memory at the beginning of your program, and control the use of it yourself. Programmers often write functions called my_malloc() and my_free() which simply keep track of pointers to this block of memory. However you choose to manage memory, be careful about fragmenting it.
Allocate an array of a few thousand bytes and use it over and over. Temporary bitmaps, such as menu art, can go in there, along with sound effects, masking maps, or whatever else comes and goes frequently.
Write lots of small functions
Any time you do something more than twice, write another function to handle it. The overhead of using many function calls can be offset by the liberal use of global variables.
Don't use inline functions
Inline functions are the way to squeeze more speed out of a C++ program. In some cases, class functions default to inline. This adds size to your program, because every occurrence of the function call is expanded to the whole function at compile time.
Use macros sparingly
Do this for the same reason you avoid using inline functions. The macro substitution happens at compile time, adding size to your executable program. Don't avoid macros altogether, though. They can greatly simplify your code, and add a speed boost as well. Just be careful how you use them.
Don't use unrolled loops
Unrolled loops are a speed optimization, at the expense of program size. If size is a concern, roll them back up. A compromise solution is to use partial unrolling. For example, you can execute a 1000-iteration loop 500 times but include the code twice within the loop.
Don't use compiled bitmaps
Compiled bitmaps are all the rage these days. Bitmap data is turned into assembly language instructions. They are blazingly fast, but add massive size overhead to the size of your program.
Use itoa() instead of sprintf()
There are very few times when you want formatted text output in a game. Usually the score will contain integers, and maybe some inventory or map coordinates also involve numbers. The sprintf() function does a nice job of converting numbers to text, but it also does a lot more. The unneeded code adds to the size of your program. If possible, avoid it and use itoa(). Similarly, use atoi() instead of sscanf().
Don't use floating point
This is a universal truth in games, and programmers will go to great lengths to avoid floating point math, including writing their own "fixed point" functions, which are integer simmulations of real number functions. In general, integer solutions can be found to most game design problems, usually without resorting to fixed point. For example, to turn clock ticks into seconds, you need to multiply by 18.2. This can be accomplished by multiplying by 182 and dividing by 10.
Rewrite the startup code
While I have never done this, some game programmers do it routinely. The function c0.asm can be optimized by stripping out the code you don't need.
Compress your executable
There is a freeware program called LZEXE which allows you to store your executable in a compressed format which is decompressed at load time. This saves disk space, but not RAM. The program will expand to fill up the same amount of RAM it required before the compression. There are also commercial programs that do the same job.
Use repetitive music and brief sound effects
Music and sound effects will eat up huge chunks of space. Plan these carefully. Trim them down to the bare minimum, and store them in RAM in reuseable arrays.
Turn little files into big files
Files are stored on disks in allocation units. Typically, DOS will alocate a unit of two 512-byte sectors on a floppy disk, or four sectors on a hard disk. A tiny file will fill the entire allocation unit, even if it is only a few bytes long. The rest of the allocation unit is simply unused space. On average, 512 bytes of space is wasted for each file on a floppy disk. That means if you have 10 files on a floppy disk, you have approximately 5K of wasted space. So if you can combine your bitmaps or sound effects into larger binary files, you will achieve a significant savings in disk space.
Use reuseable artwork
If your title screen and your credit screen use the same artwork, but perhaps arranged differently, you can store it in a single file and organize it at run time. If your title screen uses artwork from the game, so much the better. Recycle those files as much as possible.
Strip the palette information out of PCX files
If you use the same color palette throughout the game, then you don't need to store that information in a PCX file. The 256-color PCX files have 768 bytes at the end which hold the palette information. You can trim that off. If you have 10 PCX files all using the same palette, this will save you more than 7K.
Use RLE bitmaps
Run Length Encoding (RLE) is a simple technique for compressing bitmaps. It simply records the color, then the number of pixels of that color, in a continuous pattern starting at one corner. Depending on the artwork, this can save you considerable space both both in terms of disk space and RAM. RLE's are also very efficient in terms of speed. They can be displayed faster than an uncompressed bitmap, but not as fast as a compiled bitmap.
Don't store text as graphics
If you have a page of text, for example opening credits or a storyline, store it as character strings and display it using a bitmapped font. Don't store it in a PCX file, which would be very wasteful.
Split your sprites
If you have two sprites that are almost identical, break them up. For example, if you have a man with his hands up, and the same man with his hands down, make the man one sprite, and the hands another sprite. This will save quite a bit of sprite storage room, but it causes a slight speed degradation (you turn one blit into two blits) and it is also time-consuming and annoying to manipulate the artwork that way. If you are at the end of the development cycle, and you just need to squeeze a few more bytes out of your program, this is a good place to do it.
As you can see, many of these tips are exactly the opposite of what you would do if you were optimizing for speed. In actual practice, game programmers tend to weigh their options and make decisions based on both size and speed. These days, the equation is skewed heavily toward speed, with size being a trivial factor in development decisions. Still, it pays to know what you are trading away when you make your tradeoff decisions. A good programmer will keep both size and speed considerations in mind when designing and developing games.