What remains is to implement the animation. The naive implementation would be to keep re-drawing the image: background, mask and sprite, changing the position of the mask and the sprite in each frame. The problem with this approach is that it results in unacceptable flutter. The trick with good animation is to prepare the whole picture off-line, as a single bitmap, and then blit it to screen in one quick step. This technique is called double buffering — the first buffer being the screen buffer, the second one — our bitmap.
We'll also use Windows timer to time the display of frames.
class WinTimer {
public:
WinTimer(HWND hwnd = 0, int id = -1) : _hwnd (hwnd), _id (id) {}
void Create(HWND hwnd, int id) {
_hwnd = hwnd;
_id = id;
}
void Set(int milliSec) {
::SetTimer(_hwnd, _id, milliSec, 0);
}
void Kill() {
::KillTimer(_hwnd, _id);
}
int GetId() const {
return _id;
}
private:
HWND _hwnd;
int _id;
};
We'll put the timer in our Controller object and initialize it there.
class Controller {
public:
Controller(HWND hwnd, CREATESTRUCT* pCreate);
~Controller();
void Timer(int id);
void Size(int x, int y);
void Paint();
void Command(int cmd);
private:
HWND _hwnd;
WinTimer _timer;
View _view;
};
Controller::Controller(HWND hwnd, CREATESTRUCT * pCreate) : _hwnd (hwnd), _timer (hwnd, 1), _view(pCreate->hInstance) {
_timer.Set (100);
}
Once set, the timer sends our program timer messages and we have to process them.
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
Controller* pCtrl = WinGetLong
switch (message) {
...
case WM_TIMER:
pCtrl->Timer(wParam);
return 0;
...
}
return ::DefWindowProc(hwnd, message, wParam, lParam);
}
void Controller::Timer(int id) {
_timer.Kill();
_view.Step();
UpdateCanvas canvas(_hwnd);
_view.Update(canvas);
::InvalidateRect(_hwnd, 0, FALSE);
_timer.Set(50);
}
void Controller::Paint() {
PaintCanvas canvas(_hwnd);
_view.Paint(canvas);
}
The Update method of View is the workhorse of our program. It creates the image in the buffer. We then call InvalidateRectangle to force the repaint of our window (the last parameter, FALSE, tells Windows not to clear the previous image — we don't want it to flash white before every frame).
Here's the class View, with the three bitmaps.
class View {
public:
View(HINSTANCE hInst);
void SetSize(int cxNew, int cyNew) {
_cx = cxNew;
_cy = cyNew;
}
void Step() { ++_tick; }
void Update(Canvas& canvas);
void Paint(Canvas& canvas);
private:
int _cx, _cy;
int _tick;
Bitmap _bitmapBuf; // for double buffering
Bitmap _background;
int _widthBkg, _heightBkg;
Bitmap _sprite;
Bitmap _mask;
int _widthSprite, _heightSprite;
};
View::View(HINSTANCE hInst) : _tick (0) {
// Load bitmap from file
_background.Load("picture.bmp");
// Load bitmap from resource
_background.GetSize(_widthBkg, _heightBkg);
// Load bitmaps from resources
_sprite.Load(hInst, IDB_FANNY);
_mask.Load(hInst, IDB_MASK);
_sprite.GetSize(_widthSprite, _heightSprite);
DesktopCanvas canvas;
_bitmapBuf.CreateCompatible(canvas, 1, 1);
_cx = 1;
_cy = 1;
}