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.

Lighting and Shadows

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.

Static Lighting
These are fixed points in the game space and are pre-rendered during the map loading procedure. They are typically associated with static objects like torches and candles. Static lights are defined to have a radius of affect. It is assumed to blend in with the ambient light outside of this radius. This means that the elements within the radius of affect can easily be pre-determined. In most cases, there is enough geometry to make reasonably accurate computations as to the distance of static objects (such as walls) and floor tiles to calculate the intensity of the light at each pixel. The color of the light source is then multiplied with each pixel to create a set of unique graphic elements which then replace their unlit counterparts by reference. Some static objects have complex apparent geometries and lighting cannot be applied to them without breaking the illusion. It is best just to avoid placing such objects near a light source as part of the level design.

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).

Dynamic Lighting
These lights are associated with projectiles - mostly spell effects - such as fireballs. These are very simple pre-rendered resources that are colorized and blended with the floor tiles directly below the presumed source of light. They do not project onto the walls or any other static object. This technique has the added benefit of providing a visual cue indicating the objects elevation and relationship to other elements in the scene. In some cases the source or target characters are colorized in relationshaip to the spell effect - enhancing the illusion of a light source.

Shadows
When characters pass near a static light source, they are not only colorized by the source, but they appear to cast a shadow. This is accomplished by using the character's normal source graphic data - but rendering it with a bit of a twist.

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.