Simple Animation


The easiest way to perform something after a specific interval of time has passed is to use the TTimer component (System category). If the TTimer's Enabled property is set to True, the TTimer fires the OnTimer event after a specific amount of time has passed. The time interval (in milliseconds) is determined by the Interval property.

The following example uses the TTimer component to continuously display a series of colored rectangles.

The Interval property of the timer is set to 100 to have the OnTimer event occur every 100 milliseconds. Listing 22-17 shows the code that displays the rectangles displayed in Figure 22-17.

image from book
Figure 22-17: TTimer animation

Listing 22-17: Randomly drawing colored rectangles

image from book
procedure TMainForm.AnimatedDraw(Sender: TObject); var   i,j: Integer; begin   for i := 0 to (ClientWidth div 50) do     for j := 0 to (ClientHeight div 50) do     begin       Canvas.Brush.Color := RGB(Random(255), Random(255), Random(255));       Canvas.Rectangle(i * 50, j * 50, (i + 1) * 50, (j + 1) * 50);     end; end;
image from book

The TTimer component is not suitable for high-quality, accurately timed animation because it has a resolution of ~50 milliseconds. One way to measure time more accurately is to use the Windows API GetTickCount function (declared in the Windows unit).

The GetTickCount function retrieves the number of milliseconds that have elapsed since the system was started. So all you have to do to determine a specific interval is to save the beginning time in a variable and simply wait until a desired amount of time has passed. In the following example, the FTick variable (of type Cardinal) holds the start value:

if GetTickCount > FTick + 40 {25 fps} then begin   { your code here }   FTick := GetTickCount; end;      // if GetTickCount

Drawing Snow

Now we're going to create a simple application that simulates snow falling (see Figure 22-18). Unfortunately, I didn't have a snowflake image, so I used a smiley.

image from book
Figure 22-18: "Snow"

The application that we're going to create should work at 25 frames per second and use double-buffering to remove flickering. Double-buffering is a drawing technique that is often used, especially when drawing animated graphics, to eliminate flicker. Double-buffering involves drawing on two different surfaces: the back buffer and the screen. Double-buffering is implemented by:

  • Drawing everything on the back buffer (an off-screen image)

  • Displaying the back buffer onscreen when you're finished drawing

The first thing we have to do is declare the variables that we're going to use in this application. We need a back buffer bitmap, a snowflake bitmap, and an integer variable that will be used for timing. We also need a TPoint array to contain the coordinates of the snowflakes. Here are the variable declarations:

type   TMainForm = class(TForm)   private     { Private declarations }     Flake: TBitmap;                { the snowflake (smiley bitmap) }     FBuffer: TBitmap;              { the back buffer }     FTick: Cardinal;               { used for animation }     Snow: array[1..255] of TPoint; { let's work with 255 snowflakes }   public     { Public declarations }   end;

Now we have to create the back buffer and the snowflake images in the OnCreate event handler and also write an OnDestroy event handler to remove these images from memory when we're done.

Listing 22-18: Preparing the back buffer and randomizing the flakes

image from book
procedure TMainForm.FormCreate(Sender: TObject); var   i: Integer; begin   { create and set up the back buffer bitmap }   FBuffer := TBitmap.Create;   { make the back buffer the same size as the form - very important}   FBuffer.Width := Self.ClientWidth;   FBuffer.Height := Self.ClientHeight;   { create and load the snowflake image }   Flake := TBitmap.Create;   Flake.LoadFromFile('flake.bmp');   Flake.Transparent := True;   { set random flake coordinates; Y coordinates have to     be negative to have the flakes appear at the top     of the form at startup }   for i := Low(Snow) to High(Snow) do   begin     Snow[i].Y := -Random(Self.ClientHeight);     Snow[i].X := Random(Self.ClientWidth) + Flake.Width;   end;   { start timing }   FTick := GetTickCount; end; procedure TMainForm.FormDestroy(Sender: TObject); begin   { remove the sprite and the back buffer from memory }   Flake.Free;   FBuffer.Free; end;
image from book

All we have to do now is create one procedure that will draw the snowflakes and another that will call this procedure every 40 milliseconds to achieve the desired 25 FPS animation.

Animating a snowflake is not hard at all. You only have to do the following:

  • Make the snowflake fall down by constantly increasing its Y coordinate by a couple of pixels (randomly is best).

  • Have the snowflake randomly move left or right.

  • When the snowflake reaches the ground, set its Y coordinate to 0 or less to make it fall again (if you want to, of course).

Listing 22-19 contains the DrawFlakes method that draws all flakes on the back buffer and the back buffer on screen. The DrawFlakes method uses a constant array of values to move the flakes, rather than the Random or RandomRange functions, because using the constant array results in better movement.

Listing 22-19: Drawing the snowflakes

image from book
procedure TMainForm.DrawFlakes; const   RANDOM_MOVES: array[0..5, 0..2] of Integer = ((1, -2, -1),      (-1, 0, 1), (-1, 2, 1), (2, 1, -2), (-3, 1, 3), (2, 0, -2)); var   i: Integer; begin   { clear the back buffer }   FBuffer.Canvas.Brush.Color := Color;   FBuffer.Canvas.FillRect(FBuffer.Canvas.ClipRect);   Randomize;   for i := Low(Snow) to High(Snow) do   begin     { select a new horizontal position for each snowflake }     Snow[i].X := Snow[i].X + RANDOM_MOVES[Random(5), Random(2)];     { use Abs to get positive values and to make the flakes fall }     Snow[i].Y := Snow[i].Y + Abs(RANDOM_MOVES[Random(5), Random(2)]);     { if a flake reaches the end, recycle it }     if Snow[i].Y > Self.ClientHeight then       Snow[i].Y := -(Random(ClientHeight) div 2);     { don't let the snowflake leave the screen horizontally }     if Snow[i].X < 0 then       Snow[i].X := 0     else if (Snow[i].X + Flake.Width) > Self.ClientWidth then       Snow[i].X := Self.ClientWidth - Flake.Width;     { draw the flake on the back buffer }     FBuffer.Canvas.Draw(Snow[i].X, Snow[i].Y, Flake);   end;   { finally, display the back buffer }   Canvas.Draw(0, 0, FBuffer); end;
image from book

Now it's time to write the animation code. The best place for this code is in the application's OnIdle event handler. The OnIdle event is perfect because it allows us, by passing False to the Done parameter, to get as much CPU time as possible. Inside the OnIndle event handler, we can call the GetTickCount function to determine how much time has passed and call DrawFlakes to display the flakes.

First, drop a TApplicationEvents component on the Designer Surface and then write the code in Listing 22-20.

Listing 22-20: Animating the snowflakes

image from book
procedure TMainForm.ApplicationEventsIdle(Sender: TObject; var Done: Boolean); begin   { set Done to False to keep OnIdle working constantly }   Done := False;   { if enough time has passed, call DrawFlakes to update the screen }   if GetTickCount > FTick + 40 {25 fps} then   begin     DrawFlakes;     { remember the time the frame was drawn }     FTick := GetTickCount;   end;       // if GetTickCount end;
image from book

Drawing on the Desktop

If you want to draw on the desktop, you'll have to use several GDI functions, especially if you want to do what we are about to do: update the previous example and draw the snowflakes on the desktop. In addition, we are going to use the snowflakes to draw an image on the desktop (see Figure 22-19).

image from book
Figure 22-19: Drawing on the desktop

To successfully update the previous example, you have to know how do to the following:

  • How to use the GetDC function to retrieve the desktop's device context

  • How to use the BitBlt function to draw images on the screen

  • How to use the TransparentBlt function to draw images transparently (works only on Win98, Win2K, and later versions)

  • How to use the InvalidateRect function to refresh a window

To retrieve the desktop's device context, call the GetDC function and pass 0 as the hWnd parameter. Don't forget to call ReleaseDC to free the device context when you're done using it:

var   desktopDC: HDC; begin   desktopDC := GetDC(0);   try   finally     ReleaseDC(0, desktopDC);   end; // try end;

If you want to display a bitmap as fast as possible, you should use the BitBlt function. The BitBlt function requires you to pass a lot of parameters: handles for the destination and the source device contexts, four destination rectangle coordinates, two source coordinates, and the raster operation code:

function BitBlt(DestDC: HDC; X, Y, Width, Height: Integer;   SrcDC: HDC; XSrc, YSrc: Integer; Rop: DWORD): BOOL; stdcall; 

The Rop (raster operation code) parameter defines how the image pixels are combined with the source pixels (inverted, merged, etc.). There are several values that you can pass as the Rop parameter, but we're only going to use the SRCCOPY constant. The SRCCOPY constant is used when you want to copy the source pixels directly to the destination rectangle.

Here's a simple example that illustrates how to use the BitBlt function to display a bitmap image on the form.

Listing 22-21: Using the BitBlt function to draw bitmaps

image from book
type   TMainForm = class(TForm)     procedure FormPaint(Sender: TObject);     procedure FormDestroy(Sender: TObject);     procedure FormCreate(Sender: TObject);   private     { Private declarations }   public     { Public declarations }     B: TBitmap;   end; var   MainForm: TMainForm; implementation {$R *.dfm} procedure TMainForm.FormCreate(Sender: TObject); begin   B := TBitmap.Create;   B.LoadFromFile(ExtractFilePath(Application.ExeName) + 'image.bmp'); end; procedure TMainForm.FormDestroy(Sender: TObject); begin   B.Free; end; procedure TMainForm.FormPaint(Sender: TObject); begin   { draw the entire image in the top-left corner of the form }   BitBlt(Canvas.Handle, 0, 0, B.Width, B.Height,     B.Canvas.Handle, 0, 0, SRCCOPY); end; end.
image from book

The parameter list of the TransparentBlt function is very similar to the parameter list of the BitBlt function. The only big difference is in the last parameter. The TransparentBlt function requires you to pass the color that will be treated as transparent rather than the raster operation code. Here's the declaration of the TransparentBlt function:

function TransparentBlt(DC: HDC; p2, p3, p4, p5: Integer; DC6: HDC;    p7, p8, p9, p10: Integer; p11: UINT): BOOL; stdcall;

The InvalidateRect function can be used to invalidate a portion of or the entire window. This function accepts three parameters: the window's handle, a rectangle that defines the portion of the window that needs to be refreshed (pass nil to refresh the entire window), and a Boolean value that specifies whether or not the window's background should be erased. Here's the declaration of the InvalidateRect function:

function InvalidateRect(hWnd: HWND; lpRect: PRect; bErase: BOOL): BOOL;  stdcall;

Now that you know how these functions work, you can take a look at Listing 22-22 to see how to draw snowflakes on the desktop.

Listing 22-22: Drawing snowflakes on the desktop

image from book
unit Unit1; interface uses   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,   Forms, Dialogs, ExtCtrls, AppEvnts, XPMan, StdCtrls; type   TMainForm = class(TForm)     ApplicationEvents: TApplicationEvents;     RefreshButton: TButton;     XPManifest: TXPManifest;     procedure RefreshButtonClick(Sender: TObject);     procedure ApplicationEventsIdle(Sender: TObject; var Done: Boolean);     procedure FormCreate(Sender: TObject);     procedure FormDestroy(Sender: TObject);   private     { Private declarations }     Flake: TBitmap;     FBuffer: TBitmap;   public     { Public declarations }     FTick: Cardinal;     Snow: array[1..255] of TPoint;     procedure DrawFlakes;   end; var   MainForm: TMainForm; implementation {$R *.dfm} procedure TMainForm.FormCreate(Sender: TObject); var   i: Integer; begin   { create the back buffer and use it to store     the image the flakes will draw on the desktop }   FBuffer := TBitmap.Create;   FBuffer.LoadFromFile(ExtractFilePath(Application.ExeName) + 'back.bmp');   { create and load the snowflake }   Flake := TBitmap.Create;   Flake.LoadFromFile('flake.bmp');   for i := Low(Snow) to High(Snow) do   begin     Snow[i].Y := -Random(Screen.Height);     Snow[i].X := Random(Screen.Width) + Flake.Width;   end;   { start timing }   FTick := GetTickCount; end; procedure TMainForm.FormDestroy(Sender: TObject); begin   Flake.Free;   FBuffer.Free;   { refresh the desktop }   InvalidateRect(0, nil, True); end; procedure TMainForm.DrawFlakes; const   RANDOM_MOVES: array[0..5, 0..2] of Integer = ((1, -2, -1),      (-1, 0, 1), (-1, 2, 1), (2, 1, -2), (-3, 1, 3), (2, 0, -2)); var   i: Integer;   desktopDC: HDC; begin   { retrieve the desktop device context }   desktopDC := GetDC(0);   try     for i := Low(Snow) to High(Snow) do     begin       { modify the portion of the desktop the flake passed }         BitBlt(desktopDC, Snow[i].X, Snow[i].Y, Flake.Width,         Flake.Height, FBuffer.Canvas.Handle, Snow[i].X, Snow[i].Y, SRCCOPY);       { select a new horizontal position for the snowflake }       Snow[i].X := Snow[i].X + RANDOM_MOVES[Random(5), Random(2)];       { make the snowflake fall down }       Snow[i].Y := Snow[i].Y + Abs(RANDOM_MOVES[Random(5), Random(2)]);       { if flake reaches the end, recycle it }       if Snow[i].Y > Screen.Height then         Snow[i].Y := -(Random(Screen.Height) div 2);       { don't let the snowflake leave the screen }       if Snow[i].X < 0 then         Snow[i].X := 0       else if (Snow[i].X + Flake.Width) > Screen.Width then         Snow[i].X := Screen.Width - Flake.Width;       { draw the flake }       TransparentBlt(desktopDC, Snow[i].X, Snow[i].Y,         Flake.Width, Flake.Height, Flake.Canvas.Handle,         0, 0, Flake.Width, Flake.Height, clWhite);     end;         // for   finally     ReleaseDC(0, desktopDC);   end;          // try end; procedure TMainForm.ApplicationEventsIdle(Sender: TObject; var Done: Boolean); begin   Done := False;   if GetTickCount > FTick + 40 {25 fps} then   begin     DrawFlakes;     FTick := GetTickCount;   end;          // if GetTickCount end; procedure TMainForm.RefreshButtonClick(Sender: TObject); begin   InvalidateRect(0, nil, False); end; end.
image from book



Inside Delphi 2006
Inside Delphi 2006 (Wordware Delphi Developers Library)
ISBN: 1598220039
EAN: 2147483647
Year: 2004
Pages: 212
Authors: Ivan Hladni

Similar book on Amazon

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net