You may have seen Ray Harryhausen credited with Technical Effects (meaning special effects) for his amazing stop-motion animation in movies like Mighty Joe Young, It Came From Beneath The Sea, and The Beast From 20,000 Fathoms. Well, this column isn't really about special effects, just technical game details and behind-the-scenes programming stuff that we thought you might find interesting. We'll be updating it regularly, or whenever Lars thinks of something.
08/28/03: GO FETCH
There's a basic problem of trying to fit a 100lb bitmap into a 10lb screen. Our city maps are huge, with 10,000 tiles, and we've got tons of animation...probably over 50,000 frames to load during the most complex maps, monster and scenario combinations. Not including sound, music, and other assets, those bitmaps can take up several hundred megabytes. We don't want to load them all in at the same time, as that would push our system requirements up to at least 512MB systems and also make the load time excruciating for slower hard disks. Loading the assets on demand is a lot more forgiving on RAM, but the performance suffers terribly as the framerate pauses waiting for the hard disk to seek and transfer required data. My solution is a background task, designed to stream data from the hard disk while the game is playing, without interrupting framerate. While the prefetcher thread is waiting for the hard disk to seek or transfer, the game can continue rendering the next frame.
Combined with that is a bitmap manager that tracks how fresh bitmaps are, and dumps them from memory based on their age. The logic processing can do a reasonable job of predicting what assets its likely to need, and can then inform the prefetcher thread to make sure those assets are going to be around soon. For example, map tiles are a huge chunk of memory, but there isn't much reason to keep all of them in memory when only a few percentage of them are visible at any time, and only a few percentage more are likely to be displayed soon. The monster's death animation is likely to only be needed soon when the monster's health falls below a given threshold. Some graphics need to be kept around permanently, but I don't need to solve the problem completely--just enough so we run comfortably and smoothly on our target 256MB systems. The system is tuneable and dynamic, so for systems with more RAM, things are purged less aggressively and therefore less likely needed to be fetched from disk later.
Map tiles have an exception however...terrain scars like craters, footprints, bloodstains, etc. are actually rendered back into the tile bitmap rather than redraw every frame as an overlay (for performance). For them to be permanently persistent, I can no longer purge a tile bitmap that has had an image matted in. The system will try to keep these around longer, if possible, but on low memory systems with complex maps, it's likely some tiles that are offscreen will "heal" themselves as their bitmaps are purged and then later reloaded. I think that's an acceptable compromise for systems with less RAM (but I may look into tracking the composited graphics and recreating them when the tile is reloaded.)
I also use a compressed lossy image format which is very fast to decompress, skipping transparency and converting images to 4 bits with sliding palettes (as well as alternate palettes, for color variations with little extra cost). This allows me to compress a large map tile set from over 250MB for the raw image to under 60MB on disk. While the data does need to be decompressed to render, it does allow for some better RAM usage as well. Since the bitmaps are decompressed quickly in RAM, I can set multiple levels of purging for them--allowing the pixel ready portion to be dropped but keeping the compressed data around so I don't need to touch the disk if that bitmap is needed again. Or, if the bitmap gets really stale, I can drop all the data.