![]() |
|
Timers and AnimationExample: anim_one ![]() Setting upBefore we get things animated, we need to set up a structure to store the position of the ball between updates. This struct will store the current position and size of the ball, as well as the delta values, how much we want it to move each frame. Once we have the structure type declared, we also declare a global instance of the struct. This is ok since we only have one ball, if were were going to animate a bunch of them, you'd probably want to use an array or other container (such as a linked list) to store them in a more convenient way. CONST BALL_MOVE_DELTA = 2; TYPE BALLINFO = RECORD width : INTEGER; height : INTEGER; x,y : INTEGER; dx,dy : INTEGER; END; VAR g_ballInfo : BALLINFO ; We've also defined a constant Now we need to initialize this structure after we load our bitmaps: VAR bm : BITMAP; FUNC GetBITMAP(g_hbmBall, bm); g_ballInfo.width := bm.bmWidth; g_ballInfo.height := bm.bmHeight; g_ballInfo.x := 0; g_ballInfo.y := 0; g_ballInfo.dx := BALL_MOVE_DELTA; g_ballInfo.dy := BALL_MOVE_DELTA;
As mentioned in previous chapters, the
The ball starts off in the top left corner, moving to the right and down according to the Setting the TimerThe easiest way to add a simple timer into a window program is with CONST ID_TIMER = 1; ret := SetTimer(hwnd, ID_TIMER, 50, CAST(TIMERPROC,NIL)); IF ret = 0 THEN FUNC MessageBox(hwnd, "Could not SetTimer()!", "Error", MB_OK BOR MB_ICONEXCLAMATION); END; Here we've declared a timer id so that we can refer to it later (to kill it) and then set the timer
in the
We've set the timer to elapse every 50 milliseconds, which results in approximately 20 frames per second. Approximately
because like I said, Animating in WM_TIMERNow when we get | WM_TIMER : FUNC GetClientRect(hwnd, rcClient); UpdateBall(rcClient); FUNC RedrawWindow(hwnd,NIL_RECT,NULL_HRGN,RDW_INVALIDATE);
I've put the code for updating and drawing the ball in their own functions. This is good practice, and
it lets us draw the ball from either PROCEDURE UpdateBall(VAR prc : RECT); BEGIN INC(g_ballInfo.x,g_ballInfo.dx); INC(g_ballInfo.y,g_ballInfo.dy); IF g_ballInfo.x < 0 THEN g_ballInfo.x := 0; g_ballInfo.dx := BALL_MOVE_DELTA; ELSIF g_ballInfo.x + g_ballInfo.width > prc.right THEN g_ballInfo.x := prc.right - g_ballInfo.width; g_ballInfo.dx := -BALL_MOVE_DELTA; END; IF g_ballInfo.y < 0 THEN g_ballInfo.y := 0; g_ballInfo.dy := BALL_MOVE_DELTA; ELSIF g_ballInfo.y + g_ballInfo.height > prc.bottom THEN g_ballInfo.y := prc.bottom - g_ballInfo.height; g_ballInfo.dy := -BALL_MOVE_DELTA; END; END UpdateBall; All this does is some basic math, we add the delta value to the x position to move the ball. If the ball goes outside the client area, move it back in range and change the delta value to the opposite direction so that the ball "bounces" off the sides. PROCEDURE DrawBall(hdc : HDC; VAR prc : RECT); VAR hdcBuffer : HDC; hbmBuffer : HBITMAP; hbmOldBuffer : HBITMAP; hdcMem : HDC; hbmOld : HBITMAP; BEGIN hdcBuffer := CreateCompatibleDC(hdc); hbmBuffer := CreateCompatibleBitmap(hdc, prc.right, prc.bottom); hbmOldBuffer := SelectBitmap(hdcBuffer, hbmBuffer); hdcMem := CreateCompatibleDC(hdc); hbmOld := SelectBitmap(hdcMem, g_hbmMask); FUNC FillRect(hdcBuffer, prc, CAST(HBRUSH,GetStockObject(WHITE_BRUSH))); FUNC BitBlt(hdcBuffer, g_ballInfo.x, g_ballInfo.y, g_ballInfo.width, g_ballInfo.height, hdcMem, 0, 0, SRCAND); FUNC SelectBitmap(hdcMem, g_hbmBall); FUNC BitBlt(hdcBuffer, g_ballInfo.x, g_ballInfo.y, g_ballInfo.width, g_ballInfo.height, hdcMem, 0, 0, SRCPAINT); FUNC BitBlt(hdc, 0, 0, prc.right, prc.bottom, hdcBuffer, 0, 0, SRCCOPY); FUNC SelectBitmap(hdcMem, hbmOld); FUNC DeleteDC(hdcMem); FUNC SelectBitmap(hdcBuffer, hbmOldBuffer); FUNC DeleteDC(hdcBuffer); FUNC DeleteBitmap(hbmBuffer); END DrawBall; This is essentially the same drawing code as the past few examples, with the exception that it gets the
position and dimentions of the ball from the Double BufferingWhen doing your drawing directly to the
This is terribly distracting, and we can solve it simply by doing all the drawing in memory first, and then
copying the completed masterpiece to the screen in a single
To do this, we create a temporary hdcBuffer := CreateCompatibleDC(hdc); hbmBuffer := CreateCompatibleBitmap(hdc, prc.right, prc.bottom); hbmOldBuffer := SelectBitmap(hdcBuffer, hbmBuffer); Now that we have a place to draw to in memory, all of the drawing operations use FUNC BitBlt(hdc, 0, 0, prc.right, prc.bottom, hdcBuffer, 0, 0, SRCCOPY); That's it, and we clean up our Faster Double BufferingIn this example I am creating and destroying the bitmap used for double buffering each frame, I did this basically because I wanted to be able to size the window so it's easier to just always create a new buffer than to track when the window position changes and resize the buffer. It would be more efficient to create a global double buffer bitmap and either not allow the window to resize or only resize the bitmap when the window resized, instead of creating it and destroying it all the time. It's up to you to implement this if you want to optimize the drawing for a game or something. Killing the TimerWhen our window is destroyed, it's a good idea to release all resources we used, and in this case that
includes the timer we set. To stop it, we simply call FUNC KillTimer(hwnd, ID_TIMER); Copyright © 1998-2011, Brook Miles. All rights reserved. Adapted for Modula-2 by Frank Schoonjans, with permission. |