The Siege Engine

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:

The Map
The map consists of two layers of tiles. A bottom layer consisting of rectangular base tiles and an upper layer of decorative diamond tiles. The diamond tiles are subdivided into four quandrants which are aligned to the diagonals of the base tiles. Both base and diamond tiles are twice as wide as they are tall in order to match the isometric perspective. The map buffer is the same size as the screen and is stored in VRAM. Since the actual play area is smaller than the screen, the map can simply be shifted as it is drawn to the display, when the shift exceeds the dimensions of a full tile, the map buffer is copied onto itself in the new shifted position and the exposed edge is updated with the new tiles. Tiles are given priority load into VRAM, ensuring that they can be drawn very quickly as the game map is scrolled.

Static Objects
Also maintained on the map buffer is a collection of static objects. These are objects that are not animated and may not be interacted with - mostly walls and other environmental objects. Each row of pixels within a particular game map contains a linked list of static objects whose base line falls upon that row. This list is constructed at load time and does not change. This keeps the objects in the correct sorted order and provides an easy way to look up which objects are currently on the screen and might need to be updated should a moving object be moved behind one. Static objects are loaded into VRAM giving priority to the most common. This optimization is computed as a part of the level design process.

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.

Moving Objects
After the map buffer is copied to the frame buffer, the characters, spells, doors, and other interactive objects are copied to the frame buffer. The frame buffer must then be touched up with any static objects which happen to appear in front of the moving objects. This update usually includes only small portions of the screen. Moving objects are not stored in VRAM, but instead as a collection of RLEs. RLEs can be drawn efficiently directly from their compressed state allowing for a large variety of animations to be stored within the somewhat limited memory of computers of that day. Indeed, this was one the innovations that enabled SoA's rather remarkable character layering system.

The interface
After the active play area is prepared, the interface elemts rae copied onto the frame buffer. The interface elements served the additional function of compensating for one of the engine's shortcomings. The playable are must be one full tile smaller than the map buffer, which in turn must not be larger than the screen area due to hardware limitations. The SoA interface includes both a vertical and horizontal panel for this reason.

When all is ready, the frame buffer is flipped to the screen. The game mechanics are then polled, movement is recomputed, and the cycle begins again.

Modified RLE

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:

Layering System

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.