Welcome to the Power Programming series
All right, you know about GET and PUT. Maybe even made a few games with them. And with the introduction of such libraries as Blast!, Dash, and DirectQB, you have powerful graphics routines at your disposal.
But, nobody has really addressed the issue of managing all those tiles and sprites you have. Well, fortunately for you, I wrote this article just for that! In this article, you'll learn how to calculate array sizes for your sprites— explanations included so you don't have to memorize stupid formulas that don't help at all. You'll also learn how to store multiple sprites in an array—and how to use two-dimensional arrays for all your same-sized tiles. You'll also know how to save all your sprites into files and simply load them up straight into your PUT arrays! No more hacks of drawing your sprites at the start of your program.
This will be a first in an informal series of articles that deal with power programming.
ArraySize = (Width * Height + 4) / 2 - 1
But what does it all mean? Well, let's suppose that we have a 16x16 tree sprite to GET to an array. Since we're in Screen 13, each pixel is a byte in size (can take any of the 256 possible colors). So, we would need 16 * 16 = 256 bytes to store the image alone. But because GET needs space to store the sprite's width and height, each of which, being WORD values, occupy two bytes, we need an additional 4 bytes for a total of 256 + 4 = 260 bytes. That's what the plus 4 in the formula means.
If we are dimensioning an INTEGER array (where each element is two bytes in size), we would need an array with 130 elements (260 / 2 = 130), hence the divide by 2 in the formula. Here's the code to dimension the tree sprite array:
We used 129 in the DIM statement bacause there is already an element 0 (the reason why we're subtracting 1 in the formula).
If the sprite's size in bytes were an odd value, we round up. For example, if we have a 15x15 sprite, the sprite would occupy 15 * 15 + 4 = 229 bytes which will occupy 229 / 2 = 114.5 or 115 INTEGER array elements. Thus, we dimension the array this way:
Storing Multiple Sprites In An Array
The coder is obviously using one array per sprite. It would be better to store everything in as few arrays as possible so that everything would become clearer. Besides, this is especially suited for tile engines where each tile is the same size. This is also done so that we can store numerous sprites in one file using BSAVE and BLOAD.
The solution lies in the GET syntax. We can indicate in which element of the array to start storing the sprite by simply adding subscripts to the array name. For instance this code actually stores the sprite in the first element, 0.
GET (0, 0)-(15, 15), Tree
So it is the same thing as writing
GET (0, 0)-(15, 15), Tree(0)
If we dimension an array to have 260 elements (520 bytes), we can store two 16x16 sprites in it!
You'll notice that we stored the second sprite starting in element 130 since the first sprite already occupies elements 0 to 129 (130 elements, 260 bytes). The second sprite would then occupy elements 130 to 259.
You can extend this concept to store as many sprites as you want in one array. However, the array cannot exceed 64K in size (good for 252 16x16 sprites) unless you use the /AH switch in QB4.5 and the $DYNAMIC metacommand. Also, be careful that you don't overlap sprites in the array. You can leave gaps, but never overlap them.
An Application: Tile Engine Drawing Routine
For example, we have a Map() array that contains the tile numbers of each part of the map:
So if Map(13, 14) contains a 4, then there's a rock at that position.
If we organize the sprite array so that the first tile is grass, the second tile is a tree, etc., we can simplify drawing the tiles: (Assume that we have 16x16 tiles)
' Draw a 10x10 portion of the map
' Calculate where the tile is stored
' Draw the tile on the screen
It's very simple, isn't it?
If we're going to use two-dimensional arrays, accessing the tiles will become much, much easier. How do we do it? Instead of dimensioning a one-dimensional array, use a two-dimensional array where the size of the first dimension is the size of the sprite and the second dimension is the number of tiles. For example,
DIM Tiles(129, 19)
This statement will dimension an array that can hold 20 16x16 sprites (figure out why). To store the sprites, just use this
GET (...), Tiles(0, TileNumber)
where TileNumber is the number of the tile you're GETting. PUTing them works the same way.
This method of using two-dimensional array works because the first dimension elements, (0, x), (1, x), (2, x), etc., are contiguous in memory (i.e., they are next to each other).
THE GET FORMAT
Offset Length Description (bytes) (bytes) ------- ------- ------------------ 00 02 WORD width in bits 02 02 WORD height in pixels 04 XX sprite image dataThe sprite data is arranged much like what you would expect. The pixels representing the top row in the sprite are first, followed by the pixels in the next row and so forth, down to the bottom row. Knowing the GET format is essential if you're planning on making your own sprite routines which are compatible to BASIC's GET and PUT routines.
BSAVE AND BLOAD
BSAVE and BLOAD are two memory statements whose purpose is to transfer data between RAM and files. BSAVE lets you store parts of the memory to a file and BLOAD gets from the file and dumps it to the memory. To be able to fully use these commands, you first need to know all about Segments and Offsets, so if you don't know anything about these terms, don't read on until you do. (See the first part of Petter's ASM article in issue 4 for a nice introduction to Segments and Offsets)
filespec Filename (path optional) of the file to save to
or load from.
An example, to quickly save the screen (in SCREEN 13) to a file, you can use the code below. When you run the code, you will now have a file, SCREEN.GFX, in the default directory containing the memory image of the screen. (The palette is not saved, BTW.)
DEF SEG = &HA000
The Screen 13 pixels are stored in memory at the address A000:0000 and is 64000 bytes long (320 * 200 = 64000). The segment A000 is a hex value and thus, the value in the DEF SEG statement contains a prefix (&H) to indicate that it is in hexadecimal. DEF SEG is a command that changes the default data segment for use with such commands as PEEK, POKE, BSAVE, BLOAD, and CALL ABSOLUTE. It has this syntax:
DEF SEG [=segment]
It is usually a good idea to restore the original default segment by executing another DEF SEG without attributes. You can omit this if you know what you are doing.
There is a quirk with BSAVE that you should know. If you specify a file without an extension, BASIC automatically assumes a .BAS extension, which I would say is fairly stupid. Also, the if the file you specify in BSAVE exists, it is completely replaced, so beware!
To load the screen back use this code:
DEF SEG = &HA000
If you omit the offset in the BLOAD statement, BASIC will assume the segment and offset you got the image from (with BSAVE). Omitting the offset is not recommended unless the location will not change. Since the video RAM is more or less fixed in location (it has to be), you can simply use this statement instead of the code above:
VARSEG And VARPTR
Number% = 10
The example above stores the contents of the variable, Number%, into the file NUMBER.TMP.
You can specify any variable as the variablename. This includes array elements and record names. Some more examples using VARSEG:
DIM Map(1 to 10, 1 to 10)
' Get the segment of the first element of Map
' Get the segment of the (3,4) element of Map
DIM Palette(255) as RGB
' Get the segment of the red attribute of color 100
Since many variables change locations often, it is recommended that you use the values returned by VARSEG and VARPTR immediately.
Note: If you want to get the offset of a dynamic-length string variable, use SADD instead of VARPTR since using VARPTR returns the offset of the string's descriptor, not the string itself.
Saving Those Sprites
DIM Tiles(129, 19)
To load them back in the actual game, use this:
DIM Tiles(129, 19)
You'll notice that we specified the offset in the BLOAD statement since it is not guaranteed that the array will be in the exact same location it was when we saved it.
The BSAVE File Format
BSAVE FILE HEADER Offset Length Description (bytes) (bytes) ------- ------- ------------------- 00 01 BYTE Special BSAVE value 01 02 WORD Original segment 03 02 WORD Original offset 05 02 WORD Length of memory imageThe original segment:offset is the memory address the memory image was taken from. Immediately after the header comes the memory image.
Back to Top