this tutorial will work with borland kylix
If there is one topic that is the most requested, it's this. If you take a general look around Delphi forums (or indeed, my Inbox) you'll find many a person asking how to get rid of the flicker when they move images around in their games. In our DelphiX tutorials we've already covered this, but when it comes to discarding components such as these and just coding what you want to do, it can be a bit harder. So here is one method of achieving just that, and I can only apologise for not having done this tutorial a long time ago :-)
To achieve our flicker-less animation we're going to need some TImages, TPaintBoxes and a TTimer. This is a pretty complicated tutorial, but I'll try and explain every step as well as I can. You should note that this is just one method of achieving this - there are many others!
We use three of these in this example. The first will hold our Background image (which we'll call BackgroundImage), the second our Sprite image (SpriteImage), and the third our Sprite's Mask image (MaskImage). The first two of these are self-explanatory but the third you may not be sure of. Our Mask image will be the same size as our Sprite image, and any areas where we want transparency the colour should be white, and areas where we don't the colour should be black. This is demonstrated below.
The Sprite Image
The Mask Image
The point of a mask image is to allow us to define exactly where transparency should exist. We start by drawing our masked sprite and then we draw the actual sprite over the top. Without a mask we'd find that our whole sprite slightly transparent (try it later when we've developed our code), which may be desirable in certain situations, but is not here. When you add these images it doesn't matter where you put them because we'll be setting the Transparent property to false in all three TImages. This is because the images are merely 'holders' for the images which our PaintBoxes will use. You need to make sure you keep the Transparent property false as we're handling transparency using the above mask image system.
We need two of these. One will hold our buffer image and one which will finally show the results on the form. The buffer image will be where we perform all our movements and drawing, while the other box will just show the results of each set of changes made. The following actions will be performed then, calling our buffer - BufferBox, and our viewing area - ViewingBox:
- Draw our background image onto the BufferBox
- Draw our mask sprite image in its latest position over the background
- Draw our sprite image over the mask sprite image just drawn
- Copy the BufferBox to our ViewingBox
It is only at the last stage that the user sees anything happen. Both our BufferBox and ViewingBox need to be exactly the same size as our Background Image. You must also set the visible property to false on our BufferBox and true on our ViewingBox.
As with the TImages, it doesn't matter where you place your BufferBox. With the ViewingBox though you must place it where you want the user to see the final result.
This needs to be set up much as you've always done, just set the Interval property to about 20, and the Enabled property to true. I'll show you what code to place in the OnTimer event shortly.
Bringing it all together
Assuming you've added all these components then with their respective names, the first thing we need to do is set up all our variables and even throw in a constant for good measure. The following variables need to be placed in the private section of our code.
StartX : Integer;
StartY : Integer;
function SpriteRect( X, Y: Integer ): TRect;
The first three variables here set up our three TRects which we will be using to hold our background area, our sprite's image area, and our sprite's mask area respectively. The two variables after this will set our coordinates at which the sprite will begin, and finally, the function SpriteRect will allow us to easily make adjustments to our sprite on the screen. We'll come back to this function shortly. We also need to set up a constant in which we set the width our sprite will be. If you're not familiar with constants, basically they are similar to variables except that of course, you can't vary them. When you declare a constant, you declare its value at the same time. The following code shows this, and should be placed between the uses and types sections of your code.
SpriteWidth : Integer = 28;
Okay, so now we've set up our program we can get down to some coding. We'll start with setting the code for our FormCreate procedure. Double-click in an empty area of your form to create the template code for this procedure and enter the following code.
procedure TForm1.FormCreate(Sender: TObject);
StartX := 10;
StartY := 440;
AreaRect := Rect(0,0,BackgroundImage.Width,BackgroundImage.Height);
ImageRect := Rect(0,0,SpriteWidth,SpriteImage.Height);
MaskRect := Rect(0,0,SpriteWidth,SpriteImage.Height);
The first two lines set the actual values for our starting coordinates, in this case (10,440). After this we set the up our AreaRect, ImageRect and MaskRect. What we're basically doing here is setting the size of our rectangle by specifying the top left coordinates of our rectangle, and then the bottom right coordinates. The coordinates here are not to do with where they will be placed on our playing area, but rather the area of the images that we will provide them later, that they should be using. I've tried to demonstrate this below if you can make it out. The bright green rectangle represents which area of our sprite image would be selected, depending on the code.
ImageRect := Rect(0,0,SpriteWidth,SpriteImage.Height);
ImageRect := Rect(SpriteWidth,0,SpriteWidth*2,SpriteImage.Height)
I hope that makes sense. You should note that at this point we haven't actually given our TRects an image to show, we've just told it where it should be looking on our image when we do. Before we get on to the part in our code where we actually apply all our images, we need to set up our function that we declared earlier. The following code does just that, and you should add it next.
function TForm1.SpriteRect(X, Y: Integer):TRect;
Result := Rect(X,Y,X+SpriteWidth,Y+SpriteImage.Height);
When we call SpriteRect shortly we'll be providing it with the current coordinates at which our sprite has moved to. It will return the position at which our sprite should now be, and its size. You'll see why this is useful next.
We now need to set up our OnTimer event to finish the program off. In this event we'll be putting our main code as follows.
procedure TForm1.Timer1Timer(Sender: TObject);
BufferBox.Canvas.CopyMode := cmSrcCopy;
BufferBox.Canvas.CopyRect(AreaRect, BackgroundImage.Canvas, AreaRect);
BufferBox.Canvas.CopyMode := cmSrcAnd;
BufferBox.Canvas.CopyRect(SpriteRect(StartX,StartY), MaskImage.Canvas, MaskRect);
BufferBox.Canvas.CopyMode := cmSrcPaint;
BufferBox.Canvas.CopyRect(SpriteRect(StartX,StartY), SpriteImage.Canvas, ImageRect);
StartX := StartX + 1;
ViewingBox.Canvas.CopyMode := cmSrcCopy;
ViewingBox.Canvas.CopyRect(AreaRect, BufferBox.Canvas, AreaRect);
Let's go over each area of the code then, as it's pretty complicated. The first thing we do then is set up our background. The first line sets our copy mode to cmSrcCopy. This mode means that the source bitmap will be copied to the canvas of our BufferBox. On the next line we use CopyRect to copy the BackgroundImage which we set up earlier. In the brackets, the first entry is the location on the canvas where the background should be placed. Due to the fact that we want our background to just be at the coordinates of AreaRect, in this case we provide AreaRect. We then provide the name of our image's canvas, in this case BackgroundImage.Canvas. Finally, we provide the source canvas, which is the same as our destination canvas in this case.
In the next block of code we perform a similar operation, this time to draw the mask sprite image over the background at the current position it is at. This time we set the CopyMode to cmSrcAnd. This allows us to add our mask to the background. We then set the location on the BufferBox where the sprite mask should be placed first in the brackets. To do this we make use of our SpriteRect function to get it to return the coordinates on our BufferBox where the sprite should be, based on the current StartX and StartY coordinates. After that we provide the location of our MaskImage.Canvas, and set the source canvas to MaskRect as this rectangle tells us which part of the whole MaskImage we are needing to use.
Next we need to draw the sprite over the masked sprite we just drew. This time the CopyMode is set to cmSrcPaint which allows us to add the sprite over the masked image with all the transparency set correctly. The rest of this line is similar to before so you should be able to work this out yourself :-)
At this point we still haven't actually drawn anything to the screen itself, so running the program would just show an empty form. Before we move everything in our BufferBox into the ViewingBox though, we'll add one to our StartX coordinate so that next time the OnTimer event is called, the sprite will be drawn one pixel to the right. You can see this done in the code with the one line StartX := StartX + 1;
So, to display the results of everything we've done we need to set the CopyMode of our ViewingBox to cmSrcCopy so that the whole contents of our BufferBox can be copied into our ViewingBox. In our CopyRect line, this time we provide the whole AreaRect as the destination, the canvas of our BufferBox as the image we're wanting to copy, and the AreaRect as the source. This completes all the code we need, and if you run the program now you'll see our sprite moving flicker-free across the screen in front of the background image. Nice! To summarise what we've just done then, the following steps were used to show each frame of animation:
- Copy BackgroundImage to the BufferBox
- Copy MaskImage onto the BufferBox's background at its latest coordinates
- Copy SpriteImage over the MaskImage in the BufferBox
- Increase the X coordinate by one for next time
- Copy the contents of BufferBox, over that of the ViewingBox
Hopefully that makes sense to you. In the next part, we'll take what we've learnt a step further by making our pacman sprite open and close his mouth as he moves along.