Games
About Me
My Blog
Development
Forums
Game Development

Pixie Tutorial 2 - Bouncing Ball

Welcome to the second Pixie tutorial. Did you check out the first one? This time, we will display a picture on the screen, with a bouncing ball on top, and in the process learn a lot about how Pixie handles pictures and files. I will base this tutorial on the Hello World project we created in the last tutorial, so have those project files handy.

This article is rather lengthy in the theory section, so if you're the more hands-on type of person, feel free to skip forward to the more practical bit towards the end: the actual code for this is rather simple. I've found though, that knowing these types of things will make it easier to do stuff in the long run, so I recommend at least skimming through this...

Pixie and Pictures

There's a few different concepts you need to be aware of when working with pictures in Pixie. For starters, there's several different classes related to pictures, and I guess it can look a bit confusing at first, but once we're through with these explanations, it will all seem rather simple.

When we're done, you'll know the difference between, and when to use, the following Pixie classes:

I will use the term picture when talking about pictures and images in general, as opposed to any of the specific Pixie classes listed above.

In the last tutorial, we looked a bit at the Bitmap class, and used that as our main screen buffer, filling it with a solid red color and drawing letters onto it. The Bitmap is of particular interest, as it is the only type of picture that can be sent to the screen to be displayed. It is also the only type of picture we can draw to; the other types are purely source pictures (and so is the Bitmap, in addition to being a target picture, meaning you can both draw Bitmaps and draw onto them).

The Bitmap, Color Formats and Helper Functions

A Bitmap is just a collection of pixels, with a specified width and height. Each pixel is a 16 bit RGB value, with the top five bits specifing the red color component, the middle six bits specifing the green color component, and the low five bits describing the blue color value. We call this color format RGB565, and it is stored in the 16 bit datatype unsigned short .

Often when working with colors, we're used to specify R, G and B with a value between 0 and 255 for each component. In this color format, each component takes 8 bit, so we call it RGB888, and it is stored in the 32-bit datatype unsigned int (as there is no 24 bit datatype, we're wasting 8 bits, but we'll just live with that...).

In Pixie, there is a helper function to convert between 32-bit colors in RGB888 format to 16-bit colors in RGB565 format; and it's called RGB32TO16. There's one to convert from 16-bit RGB565 color to 32-bit RGB888 format as well, and it is obviously called RGB16TO32.

More interestingly, there's a helper function to directly calculate a 16-bit RGB565 color from three individual component values between 0 and 255, and it is called RGB16. If you wanted to specify, say, a bright orange color using this helper function, it would look like this:

unsigned short orange=RGB16(255,128,0);

The function you'll most likely be using most of the time is RGB16, but the other ones can be useful too. These helper funtions are implemented using fast bit shifts and binary operations, making them fast and efficient to use.

One thing to note about color formats, is that the Pixie engine always use the 16-bit RGB565 color formats everywhere and for everything (with one exception we will see shortly, which isn't important to this discussion anyay), so you never choose which color format to use. The 32-bit helper functions is just for your convenience, when you manually need to specify a 16-bit color value in code.

Using 16-bit colors instead of 32-bit colors makes a big difference when it comes to rendering speed. There is some difference in the visual quality, but most times it's not noticeable, and using 16 rather than 32 bits means everything takes half the space and there's half as much data to shuffle around when you draw things, and that makes a huge difference, and means you can have lots more stuff on the screen :-)

We saw in the last tutorial how to send a Bitmap to the screen for display. To draw a Bitmap onto another bitmap, you simply do:

myBitmap->Blit(screenBitmap,100,50);

to draw the Bitmap named myBitmap onto the Bitmap named screenBitmap, at the horizontal position of 100, and the vertical position of 50. Clipping is performed automatically, so you can draw the Bitmap partially outside the target, and things will still work correctly.

The AlphaBitmap and Transparency

One thing that might have occured to you, is that the Bitmap class doesn't support transparency. Nowadays, it is quite common to have an alpha-channel for your pictures, specifying the transparency level of each pixel. This is essential for soft edges and translucent effects.

The AlphaBitmap class has a collection of 16-bit pixels, just like the Bitmap, with a specified width and height. In addition, it's got a collection of 8-bit alpha values, with the same width and height. The contents of an AlphaBitmap can be rendered onto a Bitmap, and it will use the transparency information of the alphachannel to blend its pixels with those of the target Bitmap.

The AlphaBitmap is used in exactly the same way as the Bitmap:

 myAlphaBitmap->Blit(screenBitmap,100,50);

with the only difference that semi-transparent pixels are blended with the already existing pixels of the target bitmap.

Now, blending pixels is slow. Even the most optimized blending operation is slow, because you need to read three memory locations (source color, destination color, and alpha/transparency value), perform the blending, and write the new value back to a memory location you just read from (destination color), and this is slow, and there's not much you can do about that.

If a pixel is fully transparent though, you can just not draw it at all... and if one is fully opaque, you can draw it without blending, and this saves you a little, but the added overhead of the comparisons means it will still be significantly slower to render from an AlphaBitmap then from a normal Bitmap. And if you consider the typical case for sprites, you usually have a lot of pixels being fully opaque (the main part of the sprite), and a lot of pixels fully transparent (the pixels outside of the actual sprite shape), and only a few pixels being semi-transparent to different levels (the pixels along the edges of the sprite, to give it that smoother, anti-aliased look). And though an AlphaBitmap works for this, you're wasting a lot of processing power.

The RLEBitmap and Sprites

So, enter the RLEBitmap class. This is a special type of bitmap, designed specifically to be fast to draw, and small to keep in memory. It is the ideal option to use for sprites, where you usually have lots of frames of animation, and a lot of them on screen at a time.

The way RLEBitmaps work, is they analyze the pixels in the picture, and divide them into three separate parts: the fully transparent pixels, the fully opaque pixels, and the semi-transparent pixels. The fully transparent pixels are discarded, and the semi-transparent and fully opaque pixels are stored in two separate run-length encoded streams, which basically means we don't store individual pixels, but instead we store the color of a pixel, and how many times that pixel is repeated before there's a pixel of a different color.

This means we can read a source pixel, and fill several destination pixels in a go before reading the next source pixel. In the case of fully-transparent sections, we can just skip over large runs of pixels, and we don't even store any source data for those. And as we've split the pixels into two separate streams, we can start by rendering all the fully opaque pixels (which will be in majority for our typical sprite) at very high speed, as we don't have to perform any blending or even do any checks for transparency, and then render the semi-transparent pixels in a second pass, where we do blending for every pixel in the stream (though there won't be that many of these for the typical sprite).

Of course, you don't need to know all theese specifics to use the RLEBitmap, as it is used in exactly the same way as the Bitmap:

myRLEBitmap->Blit(screenBitmap,100,50);

but I think it is important to know a bit about how these things work, in order to make the right choices, and understand the effects they have. You can read a lot mor about how the RLEBitmap works, in my article on 2D Software Rendering.

The RLEBitmap has highly optimized code for rendering, and the source picture is converted to 256 colors (8 bits palette), to improve the performance of the RLE compression (by making it more likely to have longer runs of the same color, while also reducing the overall size of the data). All this is done at load time, and is key to speeding up the drawing of the RLEBitmap. For most things, 256 colors are more than enough, so always use the RLEBitmap for all your sprites, and use the Bitmap class if you have large backgrounds. If you find yourself needing to use the AlphaBitmap, make sure you have a good reason, as it will be slower than the alternatives.

The Image Class

In the first tutorial, we created our screen bitmap by creating an empty Bitmap with specified dimensions. A more common scenario though, is that you want to load a picture you've made in a paint-program or rendered to file using a 3d modelling application.

Pixie handles loading of pictures through the Image class. The Image class stores the width and height of a picture, and stores each pixel as a 32 bit value, with 8 bits each for the red, green and blue color components, and 8 bits for the alpha channel. This is the only place in Pixie where we work with the 32 bit RGB888 format (or ARGB8888 to be specific; the top 8 bits are used for th alpha value). An Image can't be used to draw from, but it can be passed to the constructor of the various bitmap classes, to create a bitmap from the image, like so:

RLEBitmap* myRLEBitmap=new RLEBitmap(myImage);
Bitmap* myBitmap=new Bitmap(myImage);

The Image class pixels will be converted to 16 bit format and stored in the new bitmap instance, and in the case of the RLEBitmap and the AlphaBitmap, the alpha component will be stored in the separate alpha channel.

Once you've created a bitmap from an Image, you can discard the Image object, as it is no longer needed - it is purely a source format, used when loading pictures.

So, how do you go about to create an Image in the first place? Well, there's two was to do it: you can either create an empty Image with a specified width and height:

Image* myImage=new Image(100,100);

and fill it in using:

myImage->SetPixel(0,0,0xff00a0c0);
myImage->SetPixel(1,0,0xff00ffff);

which is usually rather impractical. Or, you can load it in from file.

Loading Images and the Asset Class

The way you would expect to load an image, is to call some sort of Load method passing it a filename, and that's sort of what you do in Pixie too:

Image* myImage=new Image("Data/Pictures/MyPicture.tga");

However, there's a downside to working with files directly. Whenever you open a file, there's an overhead in terms of physically locating the file on the harddrive, and more importantly, on modern operating systems, such as Windows, there's significant overhead in terms of checking user permissions and similar. If you're loading a lot of files, it all adds up, and can make loading slower than it needs to be. A common practice is to bundle the data files into an archive file, which can give you significant speedups. Pixie has support for archive files, and in addition, it supports archive files which are embedded into the executable. This is quite cool, as it means your executable is totally self-contained, and your game doesn't need any additional files to run.

In order to be able to load from both normal files, archive files, and from an area in memory (for the embedded archives), in the same way, all Pixie classes loads data from an Asset, rather than directly from file. An asset is easy to create:

Asset myAsset("Data/Pictures/MyPicture.tga");

Normally, this will create an Asset from the specified file, and that's what you normally use during development. However, by calling, at the start of the application:

Asset::SetArchive("MyArchive.dat");

you can redirect all Assets to get their data from the specified archive rather than from individual files. By doing:

Asset::SetArchive(myArchiveMemoryBuffer,myArchiveMemoryBufferSize);

the assets will be loaded from the specified in-memory archive instead.

The good thing is that the code where the asset is created, stays the same in all three cases, and that's the whole point of using Assets (well, there's also the fact that different platforms use different endianness, and assets handles that transparently as well, so that the same data can be used on all platforms, with no change to the code).

Assets and Implicit Constructors

As already mentioned, you can create an Image from file using:

Image* myImage=new Image("Data/Pictures/MyPicture.tga");

But if you look at the definition of the Image class, you will see that there's no constructor which takes a filename, only one which takes an Asset as a parameter. This should really mean we have to do:

Image* myImage=new Image(Asset("Data/Pictures/MyPicture.tga"));

and that is indeed what happens, but it happens automatically, as the compiler will see that there's a way to create an Asset from a filename, and then pass that Asset to the constructor of Image.

This is a central concept of Pixie: everything uses Assets, and implicity constructors are used in several places, to make it more straightforward to work with files, while still keeping the power that the Asset abstraction provides. Of course, if you prefer, you can use the explicit Asset constructor; some people find that to be more clear and concise, but you don't have to. In any case, it's good to know what's going on under the hood.

Proprietary Picture Formats

We're almost done now with the theory part, but I thought I'd give a mention of the custom file formats available in Pixie. All the various bitmap formats, can be created directly from an Asset (file) in the proper format, like this:

Bitmap* myBitmap=new Bitmap("Data/Pictures/MyPicture.bm");
AlphaBitmap* myAlphaBitmap=new AlphaBitmap("Data/Pictures/MyPicture.abm");
RLEBitmap* myRLEBitmap=new RLEBitmap("Data/Pictures/MyPicture.rle");
Alphamap* myAlphamap=new Alphamap("Data/Pictures/MyPicture.am");

And though it is more convenient to use standard graphics file during development, it is recommended that you use the custom formats when releasing the games, as they will load much quicker (because we won't have to load them as an image and convert every pixel, or in the case of RLEBitmap, palettize and RLE-compress each picture).

I'll be uploading a utility shortly, which can be used to create the .rle, .bm, .abm and .am files, but there's a pretty straightforward way of generating them yourself in the meantime:

RLEBitmap myRLEBitmap(Image("Data/Pictures/MyPicture.tga"));
myRLEBitmap.Save("Data/Pictures/MyPicture.rle");

Enough Talking Already, Let's Write Some Code

Ok, so that was pretty long-winded and involved, especially considering how easy these classes are to use. But it is very good to be aware of how these things work, as it helps you understand what to use, when to use it and how to use it.

Now, let's get on to actually making use of these things in some actual code. First of all, download this file:

Tutorial2Pictures.zip

It contains these two bitmaps in TGA format:

The first one is 640 pixels wide, and 480 pixels high, and we will use it as a background. The picture of the basket ball is 80 pixels wide and 80 pixels wide, and it's got an alpha channel masking off the bits outside the ball. Unzip the files, and place them in the Runtime directory, where the HelloWorld executable is.

Now, let's modify our Game.h, by adding the following line to the protected section of the class declaration:

Bitmap* background_;

This is the member variable we'll use to hold the background bitmap. Next, in Game.cpp, we'll need to add the following lines to the top of the file:

#include "Image.h"
#include "Asset.h"

as we are using both the Image class and the Asset class when creating a Bitmap from file. In the Start method, add this line:

background_=new Bitmap(Image("background.tga"));

which will create a new Bitmap from an Image we load from file. And of course, if we create something, we need to destroy it when we're done, so add this to the Stop method:

delete background_;

to destroy the bitmap when our application ends. The next step is to actually render it to our screen buffer. Let's look at our current Update method:

screenBitmap_->Fill(RGB16(255,0,0));
DebugFont font;
font.Blit(screenBitmap_,10,10,"Hello World");
siPlatform_Screen->Present(screenBitmap_); 

Our background image will fill the entire screen, so there's no need to fill it with a solid red color anymore, so let's replace the Fill call with code to render the background, making our Update method look like this:

background_->Blit(screenBitmap_,0,0);
DebugFont font;
font.Blit(screenBitmap_,10,10,"Hello World");
siPlatform_Screen->Present(screenBitmap_); 

Which will simply blit (or draw) the background onto the screenBitmap, at position 0,0.

One thing we must not forget to do before we try to run this executable, is to set up Visual Studios working directory. So, if you right click on the HelloWorld project, and select Properties, and then locate Configuration Properties->Debugging and on the line where it says Working Directory you put in Runtime:

If you build and run the program now, you should see the background with our Hello World text printed on top.

Adding the Ball

To complete this tutorial, we're going to add a bouncing basket ball. To do this, start by adding the following line to Game.h, in the protected section of the class declaration:

RLEBitmap* ball_;
int ballX_;
int ballY_;
int speedX_;
int speedY_;

We're choosing to use the RLEBitmap class for the ball, as it is quite a small object (making 256 colors enough for it), with an alpha channel, while the background was a better match for the Bitmap class, as it is larger and with no alpha channel. The ballX_ and ballY_ member variables will be used to track the current position of the ball, and speedX_ and speedY_ will be used to track which direction the ball is travelling, and at what speed.

As we're referencing the RLEBitmap class, we need to add the following to the External Classes section of Game.h::

class RLEBitmap; 

to forward declare RLEBitmap, just like we did with Bitmap in the first tutorial. And as we're making use of the RLEBitmap class, we need to add this line to the top of Game.cpp:

#include "RLEBitmap.h"

Next, locate the Start method of Game.cpp, and add the following lines:

ball_=new RLEBitmap(Image("ball.tga"));
ballX_=320;
ballY_=240;
speedX_=3;
speedY_=3;

This creates an RLEBitmap from the ball Image, and we're setting the initial ball position to the center of the screen, and the initial speed/direction such that the ball will be moving downwards and to the right by 3 pixels per update.

And as always, when we create something using new, we need to destroy it with a call to delete, so add this to the Stop method:

delete ball_;

to destroy the ball bitmap when our application ends. Now, go to the Update method of Game.cpp, and add this line directly after the line that draws the background:

ball_->Blit(screenBitmap_,ballX_-40,ballY_-40);

This will draw the ball on the screen, at the position given by ballX_,ballY_. We subtract 40 pixels from both x and y, in order to get the center of the ball at the ballX_,ballY_ position, rather than getting the upper left corner of the ball bitmap at that location (remember, the ball bitmap is 80 by 80 pixels).

Next up is moving the ball. Add these two lines just after the one we just added:

ballX_+=speedX_;
ballY_+=speedY_;

Which obviously just moves the ball position by the current speed. The last thing left to do now is have the ball change direction when it reaches the edge of the screen (well, some distance from the edge actually, as we want it to look like the ball is bouncing on the wood). Add the following after the lines we just added:

if (ballX_>584)
	{
	speedX_=-speedX_;
	ballX_=584;
	}

if (ballX_<56)
	{
	speedX_=-speedX_;
	ballX_=56;
	}

if (ballY_>424)
	{
	speedY_=-speedY_;
	ballY_=423;
	}

if (ballY_<56)
	{
	speedY_=-speedY_;ballY_=56;
	}

And this is also pretty straightforward; if either the x or y position of the ball have ended up outside of our desired area, we invert the corresponding speed variable, and move it inside the valid area again.

So, that's it! Compile it, give it a run, and you'll have a ball bouncing on the screen. Pretty easy huh?

The final Game.h

The final Game.cpp