View resources, or views, contain some of the graphics for an AGI game. Unlike the picture resources, which are full-screen background images, view resources are smaller "sprites", such as animations. They are also stored as bitmaps, whereas pictures are stored in vector format.
Each view resource consists of one or more loops. Each loop in the resource consists of one or more cels (animation frames). Thus several animations can be stored in one view, or a view can just be used for a single image. The maximum number of loops supported by the interpreter is 255 (0-254) and the maximum number of cels in each is loop 255 (0-254); however, some view editors, including the most common development kits AGI Studio and WinAGI, support only 32 cels within each 16 loops in view. Yet need for greater amount of cels and loops is next to nothing, unless user intend to put countless really small animations within view, a non-hacked standard Sierra interpreter can't handle too big memory usages, even if quickly used and then discarded, say with
Views have a number of useful features:
- Transparency: Each cel has a transparent color. When the view is shown on the screen, pixels of this color will show to the background.
- Mirroring: You can set one loop to be a “mirror” of another, which means that all the cels in that loop will be mirror images of the corresponding cels in the other loop. This is handy if you have drawn an animation of a character walking right, and also want to have an animation of it walking left - you only have to do half the work (and the view takes up less memory).
- Description: This is used when a view is a picture of an inventory item. When the view is displayed using the show.obj command (i.e. when the player “examines” an object), the first cel of the first loop is displayed on screen along with the description.
Note: this section includes technical details about the view resource format. Game authors generally do not need to know this information.
View Header (7 bytes)
Byte Meaning 0 Unknown (always seems to be either 0 or 1) 1 Unknown (always seems to be 1) 2 Number of loops 3-4 Position of description (more on this later) 5-6 Position of first loop 7-8 Position of second loop (if any) 9-10 Position of third loop (if any) ... ...
Note: two of the loop references can point to the same place. This is done when the view uses mirroring (more on this later).
Loop header (3 bytes)
Byte Meaning 0 Number of cels in this loop 1-2 Position of first cel, relative to start of loop 3-4 Position of second cel (if any), relative to start of loop 5-6 Position of third cel (if any), relative to start of loop ... ...
Cel header (3 bytes)
Byte Meaning 0 Width of cel (remember that AGI pixels are 2 normal EGA pixels wide, so a cel of width 12 is actually 24 pixels wide on screen) 1 Height of cel 2 Transparency and cel mirroring (first four bits are for cel mirroring, which is discussed later; second four bits are the transparency color)
Any pixels of the transparency color will show through to the background when the cel is drawn on the screen. All cels have a transparent color, so an opaque cel must specify a transparent color that is not used in the cel itself.
The image data for each cel is stored using Run-Length Encoding (RLE) compression. This means that instead of having 1 byte for each pixel (or 1/2 byte as you would use for 16 colors), each byte specifies a color and how many consecutive pixels there are using that color. In this document, these groups of pixels are called chunks.
This method of compression is not very efficient if there are a lot of single pixels in the image (e.g., a view showing static on a TV screen), but in most cases it does save a fair amount of space.
Each line (not to be confused with a chunk) in the cel consists of several bytes of pixel data, then a zero to end the line. Each byte of pixel data represents one chunk. The first four bits determine the color, and the last four bits determine the number of pixels in the chunk. This means that a chunk can represent a maximum of 15 pixels.
AX BY CZ 00
This line will have:
- X pixels of color A (
- Y pixels of color B (
- Z pixels of color C (
- And the
00ends the line
If the color of the last chunk on the line is the transparent color, there is no need to store this. For example, if C were the transparent color in the above example, you could just write
AX BY 00. This also saves some space.
Mirroring is when you have one loop where all the cels are a mirror image of the corresponding cels in another loop. Although you could do this manually by drawing one loop and then copying and pasting all the cels to another loop and flipping them horizontally, AGI views provide the ability to have this done automatically – you can draw one loop, and have another loop which is set as a mirror of this loop. Thus, when you change one loop you change the other. This is useful if you have an animation of a character walking left and right – you just draw the right-walking animation and have another loop a mirror of this which will have the left-walking animation. Another advantage of cel mirroring is to save space â€“ it doesn't make much difference these days, but back when AGI was written the games were designed to run on 256K systems which meant that memory had to be used as efficiently as possible.
Mirroring is done by having both loops share the same cel data -- you saw above that you can have two loop references pointing to the same place. The first four bits of the 3rd byte in the header of each cel tell the interpreter what is mirrored:
|0||Specifies whether this cel is mirrored.|
|1-3||Specifies the number of the loop (from 0-7) which is not mirrored|
When the interpreter goes to display a loop, it looks at the bit 0 and sees if it is mirrored or not. If it is, then it checks the loop number – if this is not the same as the current loop, then it flips the cel before displaying it.
If you have a cel that is mirrored, you need to ensure that the number of bytes the cel takes up in the resource is greater than or equal to the number of bytes that the flipped version of the cel would take up. The reason for this is that the interpreter loads the view resource into memory and works with that for displaying cels, rather than decoding it and storing it in memory as a non-compressed bitmap. I assume that it doesn't even bother "decoding" it as such – it probably just draws the chunks of color on the screen as they are. When it has to display the flipped version of a cel, instead of storing the flipped cel somewhere else in memory, it flips the version that is there. So in memory you have the view resource that was read from the file, except that some of the cels have been changed. This is why there is mirroring information stored in each cel – the interpreter has to know what cels have been changed. When it flips a cel, it changes the loop number in the 3rd byte of the cel header to the number of the loop it is currently displaying the cel for. So when it looks at this number the next time for a different loop, it will see that the cel is not the right way round for that loop and mirror it again.
This process seems very inefficient to me. I don't know why it doesn't just draw the chunks of color on the screen back to front. But since it does it this way we have to make sure that there is enough room for the flipped cel.
It seems that not all versions of the interpreter require this, however -- I was working with version 2.917 when I was testing this out, but I noticed that version 2.440 did not require this. I will attempt to try this with all different interpreters and provide a list of which ones use this in a future version of this document. But it is best to put these bytes in just in case, as the views will still work regardless.
All the views in the game that are used as close-ups of inventory items have a description. When a player "examines" these (in some games you can select "see object" from the menu), they will see the first cel of the first loop of this view and the description of the object they are examining. This is brought up using the show.obj command.
The description is stored in plain text, and terminated by a null character. Lines are separated by an 0x0A.