The Siege Engine began as a personal project to see if I could reproduce some of the characteristics of several popular games that had been recently released, most notably, the original Diablo and Fallout. I posted the results of this experimentation on-line in the form of a collection of Delphi components. Timing was such that the small startup Digital Tome happened to be looking for a relatively inexperienced game developer - near the area in which I lived. After an enthusiastic initial meeting, I was brought on board.
The initial architecture of the core game system was no where near ready for the ambitious SoA product design. Fortunately, the episodic nature of SoA allowed the technology to develop as the story did. Each chapter release included technology updates as well. Customer interraction was amazing, and often customer feedback would influence development.
The basic structure of the game engine works as follows:
Moving objects are not drawn on the map buffer, so the map buffer, so the map buffer is seldom updated. Instead, with each frame, the map buffer is copied (with the appropriate offset) to the frame buffer.
The excellent RLE library used in SoA's original release was licensed from Dariusz Zolna of FAST Projects. He also did some very nice custom work for us with shadows and some other effects. In post-release, unofficial patches, I replaced this library with one of my own making - In part because I wanted to implement some custom blend options, but also because, well, I just like that sort of thing.
My RLE implementation does not conform to the traditional RLE specification. Central to the idea of my implementation is that runs of transparency always alternate with runs of color information. I do not include runs of single colors in the spec, so I have no need to flag an additional option. In fact neither option is flagged at all. It is just assumed that the first run is transparent, followed by a run of color, and so on. If the first run is not transparent, then the run length is zero.
Each run length is encoded with sixteen bits, and the color information is also sixteen bits per pixel. A block header indicates the beggining of each row of pixels and also removes extraneous rows from the top and bottom of the image. Decoding the row of pixels is orderly and efficient and is optimized to use Intel's built-in string instructions. Basically, read N, skip N, read N, copy N pixels, repeat until the entire width of the image has been copied, then move to the next line.
ESI is the RLE source row.
EDI is the image row destination.
DX contains the image width.
@@InnerLoop:
mov ECX,[ESI]
add ESI,4
sub DX,CX
jbe @@InnerDone
movzx EAX,CX
shl EAX,1
add EDI,EAX
shr ECX,16
@@InnerCompare:
cmp CX,DX
jbe @@NoClip
mov CX,DX
@@NoClip:
sub DX,CX
rep movsw
@@Continue:
test DX,$FFFF
jnz @@InnerLoop
@@InnerDone:
It was always the intent to have a wide variety of characters represented within the game, instead of resorting to generic character types. This placed a tremendous burden upon the art staff. Designing each new character type had become a time-consuming and repetative exercise. They say that schedule pressures are the mother of invention. The artists had developed a system by which they would render the various armor and clothing peices separately, and the characters could be generated by layering the selected items and rendering the result.
As it happened, I was trying to find a strategy for maintaining 40+ different fully rendered characters in memory. As I often do, I meandered while in deep thought, and passed by the art pit while their layering technique was being demonstrated. I began to wonder if layered art could be applied in real-time. Within an hour, I had whipped up a demo to test the idea, and it worked with minimal impact on performance. Suddenly, focus changed from rendering characters, to rendering items. Item art requires far less storage than fully rendered character art - with some items being trivial, such as belts and shoes. Now, every character could have a unique appearance, and all the while using less memory.
Characters could now be defined by a simple text file describing which layers to include. The opportunity now presented itself to link the layered art to the actual items being worn. At this point in development, the item slot system had already been defined, and now each of those slots had the power to change the character's appearance.
One minor complexity of the system was that most of the layer art was designed to fit the base human male template and would not fit some of the other templates including base human female, elf, and ahoul. The infamous xref.db handles the assignment of item art (or parts) to the other templates. In many cases, several items will map to the same art, creating less diversity for these templates.
Since SoA does not rely on any 3D rendering, the lighting techniques I employed rely largely on trickery to accomplish their effect. Three categories of effects were used and will be discussed seperately.
A variant of this technique is flicker lighting which is used sparingly within the game since it uses additional storage and is computationally more expensive. This type of lighting was limited to key areas for dramatic effect (notably, the starting area). This effect is accomplished by varying the intensity of the light source and calculating multiple sets of tiles and static objects.
Dynamic objects which pass near a fixed light source can simply be colorized according to the source's intensity and color. The locations of light sources are stored in an optimized list for this purpose (which is also useful for shadow determination - see below).
The graphic data for characters are stored as a collection of RLEs. Each frame of the RLE is pre-rendered from eight angles. Normally, the angle chosen is based on the character's facing relative to the screen. For shadows, the angle is chosen based on the character's facing relative to the light source. The run-lengths are then rendered onto a temporary bitmap where the color data is simply replaced with black. The resultant bitmap is then rotated and blended onto the map tiles extending from the character's feet. Shadows applied this way never quite line up to the character, but are close enough - particularly when the character is in motion.
It is worth noting that this technique can be applied multiple times in the same frame. This is useful if a character is within range of multiple light sources at the same time. This effect can be readily observed while running down one of the torch-lit hallways within Avalon.