The previous post in this series was on a wonderful bag of tricks used to set a custom color palette (awesome!) and change the font and font size. In this post we'll be going over the painter's algorithm, and how to actually make use of it in your game.
The painter's algorithm is a very simple priority paradigm which dictates when you place what image onto the screen.
For example suppose you have three different images; the sky, a cloud, and the sun. You want to display these images, but you don't want the sun overlapping a cloud (physically impossible), or even worse have the blue sky overlap everything. To solve this you simply draw images onto the screen starting with the one that is going to be furthest back in terms of depth, which would be the sky.
Example of drawing sky, then sun, then cloud. Correct.
Example of drawing sky, then cloud, then sun. Wrong.
If we recall the article on event handling, the article actually taught a method for producing and writing images to the screen by using header files to hold image data and a function to write images to an array (or buffer) of CHAR_INFO structures. Using the same system, you can create images in header files and then write them to the screen in the correct order so that images overlap each other at the proper times. The way you go about this is to have your array of CHAR_INFO structures for writing images to. Then once you've manipulated the array (or buffer) all you want, you then pass it to the WriteConsoleOutput function and place it onto the screen in one fell swoop.
Here's a screenshot of a demo program I wrote just for this specific purpose!
Rendering of images with transparency using painter's algorithm.
Note: Read this article for info on resizing font, this will allow
you to have square characters like in the image above (8x8).
Building from our knowledge gained in the previous posts, I've put together a nice demo that displays the images above, and lets you draw on the screen with the left and right clicks of the mouse. Lets examine some of the code:
#ifndef FILESUNH
#define FILESUNH
/* A SUN background! */
#define SUNW 15
#define SUNH 15
typedef struct
{
int width;
int height;
int chars[SUNW * SUNH];
int colors[SUNW * SUNH];
} _SUN;
_SUN SUN =
{
SUNW,
SUNH,
{
255,255,255,255,255,255,255,177,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,177,255,255,255,177,255,255,255,177,255,255,255,
255,255,255,255,177,255,255,255,255,255,177,255,255,255,255,
255,255,255,255,255,255,177,177,177,255,255,255,255,255,255,
255,255,255,255,255,177,178,219,178,177,255,255,255,255,255,
255,255,255,255,177,178,219,219,219,178,177,255,255,255,255,
177,255,177,255,177,219, 94,219, 94,219,177,255,177,255,177,
255,255,255,255,177,178,219,126,219,178,177,255,255,255,255,
255,255,255,255,255,177,178,219,178,177,255,255,255,255,255,
255,255,255,255,255,255,177,177,177,255,255,255,255,255,255,
255,255,255,255,177,255,255,255,255,255,177,255,255,255,255,
255,255,255,177,255,255,255,177,255,255,255,177,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,177,255,255,255,255,255,255,255,
},
{
0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 62, 0, 0, 0, 62, 0, 0, 0, 62, 0, 0, 0,
0, 0, 0, 0, 62, 0, 0, 0, 0, 0, 62, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 62, 62, 62, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 62, 62, 14, 62, 62, 0, 0, 0, 0, 0,
0, 0, 0, 0, 62, 62, 14, 14, 14, 62, 62, 0, 0, 0, 0,
62, 0, 62, 0, 62, 14,224, 14,224, 14, 62, 0, 62, 0, 62,
0, 0, 0, 0, 62, 62, 14,224, 14, 62, 62, 0, 0, 0, 0,
0, 0, 0, 0, 0, 62, 62, 14, 62, 62, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 62, 62, 62, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 62, 0, 0, 0, 0, 0, 62, 0, 0, 0, 0,
0, 0, 0, 62, 0, 0, 0, 62, 0, 0, 0, 62, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, 0, 0, 0, 0,
}
};
#endif /* FILESUNH */
The above code is the header file for the image of the sun. As shown in this article, you can easily create new images! The most interesting thing about this image is the 255 character code in the chars array. This code is actually used for transparency. I modified our old writeImageToBuffer code to account for transparency by adding in a single if statement. If the loop finds a character of 255, it does nothing and skips the character. This will leave whatever character was already written in that particular location alone, thus creating transparency! Take a look at the new writeImageToBuffer function:
/* Writes an image to the buffer */
void writeImageToBuffer(CHAR_INFO buffer[], int chars[], int colors[], int imageBUFFERWIDTH, int imageBUFFERHEIGHT, int xoffset, int yoffset)
{
int x, y;
/* Keep xoffset and yoffset within the screen's boundaries */
boundCheck(&xoffset, &yoffset);
for (y = 0; y < imageBUFFERHEIGHT; ++y)
{
for (x = 0; x < imageBUFFERWIDTH; ++x)
{
if (chars[x + imageBUFFERWIDTH * y] != (unsigned char)255)
{
buffer[(x + xoffset) + BUFFERWIDTH * (y + yoffset)].Char.AsciiChar =
chars[x + imageBUFFERWIDTH * y];
buffer[(x + xoffset) + BUFFERWIDTH * (y + yoffset)].Attributes =
colors[x + imageBUFFERWIDTH * y];
}
}
}
return;
}
As I said, there's simply a new if statement to make sure that the character 255 is not written onto the screen. 255 in ASCII is a character you'll more than likely find pretty useless, as it just fills the whole space with foreground, though you have the exact same thing with 219 without any nasty side affects that 255 can ensue on your code. Basically 255 is the perfect ASCII index for transparency as it's pretty much useless as anything else.
The last thing to show in this demo is the order in which you actually write images onto the buffer, being sky sun and cloud. This is really simple as shown here:
writeImageToBuffer(consoleBuffer, SKY.chars, SKY.colors, SKY.width, SKY.height, 0, 0);
writeImageToBuffer(consoleBuffer, SUN.chars, SUN.colors, SUN.width, SUN.height, 10, 5);
writeImageToBuffer(consoleBuffer, CLOUD.chars, CLOUD.colors, CLOUD.width, CLOUD.height, 15, 11);
And there you have it! By ensuring that you write images in the correct order in terms of depth, you overwrite images farther back with images closer up creating a proper sense of depth to the viewer.
The next post in this series is on a more flexible image structure format to allow a variable sized structure.
Series on creating a Windows Console game:
- Setting up the Window
- Writing to the Console
- Event Handling
- Custom Color Palette and Font Size
- Painter's Algorithm
- Variable Sized Object
- Ascii Engine