   [Win32 C++] Approach to Creating Movement/Animation
  HelloI have created a Win32 app where you create a series of targets with the left mouse then click a button to fire a laser at the targets, the laser is just a line that moves from one target to the next.Is my method of creating the look of movement or animation the best way that can be done in Win32. Is there a completely different, better, more efficient method you can suggest? Or is my way good but maybe I could imporve with 'blah' tweaks?My present algorthm is really simple: - I have a laser object that draws a rectangular region to make it look like a laser- To give the effect of movement I clear the objects region, update the objects coords, then redraw the object.- I use a timer(like a animation timer/frame rate) to determine when to redraw the laser object at a new position.I am looking for any advice on how my program could be improved in any way, criticism is welcome.Draw Laser Win32 Application - 85kb http://www.mediafire.com/?zguz1nknh5g Header File#ifndef LASEROBJECTS_H#define LASEROBJECTS_H#include <windows.h>#include <cstdlib>#include <stdio.h>#include <queue>using namespace std; /// Constants ///#define LA_DRAWLASER 50000#define LA_TIMER50001#define LA_ANIMATION 50002/// Objects ///struct target {    // Functions  target(LONG xPos, LONG yPos, int xOff, int yOff);  ~target();   void drawTarget(HDC hdc);    // Variables  POINT pos;    // Object position  int xOffset;  int yOffset;  HRGN objRgn;};struct laser {    // Functions  laser();  ~laser();  bool hasTarget();  void drawController(HDC hdc, HWND hwnd);  void drawLaser(HDC hdc, HWND hwnd);  void createTarget(HWND hwnd, int mouseX, int mouseY);  void moveToTarget(HWND hwnd);  void initiateDelay(HWND hwnd);  void ceaseDelay(HWND hwnd);  void initAnimationTimer(HWND hwnd);  void ceaseAnimationTimer(HWND hwnd);    // Variables  POINT base;  POINT top;  HRGN objRgn;  int laserWidth;  int hSpeed;  int vSpeed;    bool lButtonDown;  bool createTargetAllowed;  bool drawNewTarget;  bool drawNewLaser;  UINT delayTimerID;  UINT animTimerID;  int targetDelay;  queue <target*> targetList;  int targetDimension;  HPEN laserPen;    int sleepValue;};/// Global Functions ///void createGUI(HWND hwnd, HINSTANCE gInstance, RECT clientR);#endifObject Implementation file#include <windows.h>#include <cstdlib>#include <stdio.h>#include <queue>#include "laserObjects.h"using namespace std;/// Target Object Implementation //////    ///target::target(LONG xPos, LONG yPos, int xOff, int yOff){   // Constructor pos.x   = xPos;   pos.y   = yPos;   xOffset = xOff;   yOffset = yOff;   objRgn  = CreateEllipticRgn(pos.x-xOffset,pos.y-yOffset,pos.x+xOffset,pos.y+yOffset); // if we failed to create region   if (!objRgn) {  MessageBox(NULL,"Failed to create new target region","Error:",MB_OK|MB_ICONERROR);   }}target::~target(){   // Destructor //DeleteObject(objRgn);  Do I need to delete a HRGN?}void target::drawTarget(HDC hdc){   // Post: Draw a black dot on this objects' x,y coords int success = FillRgn(hdc,objRgn,( CreateSolidBrush(RGB(0,0,0)) )); if (success == 0) {  MessageBox(NULL,"Failed to fill target region with HBRUSH","Error:",MB_OK|MB_ICONERROR); }}/// Laser Object Implementation //////    ///laser::laser(){   // Default Constructor base.x = 0;  base.y = 100;   top.x  = 100; top.y  = 100;   objRgn = CreateRectRgn(int(base.x-1.5),int(base.y-1.5),int(top.x+1.5),int(top.y+1.5));   laserWidth = 4;   hSpeed = 4;   vSpeed = 4;    createTargetAllowed = true;   // Can we create a target   lButtonDown= false; // Is the left mouse button held down   drawNewTarget   = false;   drawNewLaser    = false;   delayTimerID    = INT_MAX;   animTimerID= INT_MIN;   targetDelay= 500;   // Timer/Delay between creating targets   targetDimension = 6;// Width & height of target ellipse   laserPen   = CreatePen(PS_SOLID,laserWidth,BLACK_PEN); sleepValue = 200;}laser::~laser(){   // Destructor  // delete all dynamic data   while (!targetList.empty()) {   delete targetList.front();    targetList.pop(); }   //DeleteObject(objRgn); Should I delete a HRGN?   DeleteObject(laserPen);}bool laser::hasTarget(){   // Post: Returns true if there are targets for the laser to shoot at   //  else falseif (targetList.size() > 0) {   return true;   }    else return false;}void laser::drawController(HDC hdc, HWND hwnd){   // Post: Controls & initiates the drawing of the laser & targets if (hasTarget()) {    if (drawNewTarget) {targetList.back()->drawTarget(hdc);drawNewTarget = false;}    if (drawNewLaser) { drawLaser(hdc,hwnd);   } }   }void laser::drawLaser(HDC hdc, HWND hwnd){   // Post: Draws line from lasers base point to its top point to appear like   //  the laser is shooting at the target    if (hasTarget()) {    InvalidateRgn(hwnd,objRgn,true);    moveToTarget(hwnd);    POINT baseLeft, baseRight, topLeft, topRight;    baseLeft.x  = base.x + (laserWidth / 2);  baseLeft.y  = base.y - (laserWidth / 2);  baseRight.x = base.x - (laserWidth / 2);  baseRight.y = base.y + (laserWidth / 2);  topLeft.x   = top.x + (laserWidth / 2);   topLeft.y   = top.y - (laserWidth / 2);  topRight.x  = top.x - (laserWidth / 2);   topRight.y  = top.y + (laserWidth / 2);    POINT laserPnts[] = {baseRight, baseLeft, topLeft, topRight};    objRgn = CreatePolygonRgn(laserPnts,4,WINDING);  FillRgn(hdc,objRgn, CreateSolidBrush(RGB(0,0,0)) );    // The below Sleep function is a real hack.  // If I dont make the program sleep for x time, then you dont see the laser line   // coz it get erased straight away, so I needed a way to see the laser line for x amt of time  // can you suggest a better way to fix this?  // Should I use different animation, drawing & movement method to avoid this?  // Or is there a simple way to fix this problem?  Sleep(sleepValue); }}void laser::moveToTarget(HWND hwnd){   // Post: Logically move laser top POINT towards a target int collision = PtInRegion(targetList.front()->objRgn,top.x,top.y); // if NOT laser collision with target | if laser is NOT at target   if (collision == 0)    {int xDist = top.x - targetList.front()->pos.x;   int yDist = top.y - targetList.front()->pos.y;    // if laser is left of target  if (xDist < 0) { top.x += hSpeed;  }  // if laser is right of target  else if (xDist > 0) {  top.x -= hSpeed;  }    // if laser is above target  if (yDist < 0) { top.y += vSpeed;  }  // if laser is below target  else if (yDist > 0) {  top.y -= vSpeed;  }   }   // Laser is at target   else {  // Delete present target & move on to next target if it exists  InvalidateRgn(hwnd,targetList.front()->objRgn,true);  delete targetList.front();  targetList.pop();   }}void laser::createTarget(HWND hwnd, int mouseX, int mouseY){   // Post: Creates a new target at cursor position & stores target in queue if (createTargetAllowed) { target *newTarget = new target(mouseX,mouseY,targetDimension,targetDimension);  targetList.push(newTarget);    drawNewTarget = true;  InvalidateRect(hwnd,NULL,false); // Send WM_PAINT to WNDPROC queue  UpdateWindow(hwnd);    // Push WM_PAINT to front of queue  initiateDelay(hwnd);   // Set timer so we cant create another target for 1/2 second }  return;}void laser::initiateDelay(HWND hwnd){   // Post: Make it so user cannot create a target for a certain amount of time   //  using a timer   createTargetAllowed = false;   delayTimerID = SetTimer(hwnd,LA_TIMER,targetDelay,NULL);}void laser::ceaseDelay(HWND hwnd){   // Post: Turn off 'target creation delay' timer    int success = KillTimer(hwnd,LA_TIMER);   createTargetAllowed = true;}void laser::initAnimationTimer(HWND hwnd){   // Post: Create timer to trigger animation of laser movement animTimerID = SetTimer(hwnd,LA_ANIMATION,80,NULL);}void laser::ceaseAnimationTimer(HWND hwnd){   // Post: Turn off animation timer int success = KillTimer(hwnd,LA_ANIMATION);   drawNewLaser = false;   //InvalidateRgn(hwnd,objRgn,true);   //UpdateWindow(hwnd);}/// Functions ////// ///void createGUI(HWND hwnd, HINSTANCE gInstance, RECT clientR){   // Post: Create Laser Application's Controls/Graphical User Interface char instructions[] = "Click and hold left mouse button down to create targets for the laser to"    "hit. Press button to fire laser when you have created your targets. Press control to see issue"    "with drawing the laser, you will see that the laser is not drawn long enough. Also the laser "   "flickers. "; HWND btDrawLaser = CreateWindowEx(0,"Button","Draw Laser",WS_CHILD|WS_VISIBLE|WS_BORDER|BS_PUSHBUTTON,  20,clientR.bottom-40,100,20,hwnd,(HMENU)LA_DRAWLASER,gInstance,NULL); HWND instruct = CreateWindowEx(0,"Static",instructions,WS_CHILD|WS_VISIBLE|WS_BORDER,  130,clientR.bottom-100,clientR.right-140,90,hwnd,NULL,gInstance,NULL);  if (!btDrawLaser) {  MessageBox(hwnd,"Failed to create button to draw laser","Error:",MB_OK|MB_ICONERROR); }}Winmain File/*  Application: Laser Example*/#include <windows.h>#include <cstdlib>#include <stdio.h>#include <queue>#include "laserObjects.h"using namespace std;// Global Variables //static HINSTANCE gInstance;laser myLaser;// Functions List //LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);int WINAPI WinMain(HINSTANCE gInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow){    WNDCLASSEX wc;    HWND hwnd;    MSG Msg;    //Step 1: Registering the Window Class    wc.cbSize   = sizeof(WNDCLASSEX);    wc.style    = 0;    wc.lpfnWndProc   = WndProc;    wc.cbClsExtra    = 0;    wc.cbWndExtra    = 0;    wc.hInstance= gInstance;    wc.hIcon    = LoadIcon(NULL, IDI_APPLICATION);    wc.hCursor  = LoadCursor(NULL, IDC_ARROW);    wc.hbrBackground = (HBRUSH)(DKGRAY_BRUSH);    wc.lpszMenuName  = NULL;    wc.lpszClassName = "Custom Class";    wc.hIconSm  = LoadIcon(NULL, IDI_APPLICATION);    // if registration of main class fails    if(!RegisterClassEx(&wc))    {   MessageBox(NULL, "Window Registration Failed!", "Error!",  MB_ICONEXCLAMATION | MB_OK);   return 0;    }    // Step 2: Creating the Window    hwnd = CreateWindowEx(   WS_EX_CLIENTEDGE,   "Custom Class",   "Laser Example",   WS_CAPTION|WS_MINIMIZEBOX|WS_VISIBLE|WS_OVERLAPPED|WS_SYSMENU,   CW_USEDEFAULT, CW_USEDEFAULT, 600, 500,   NULL, NULL, gInstance, NULL);    if(hwnd == NULL)    {   MessageBox(NULL, "Window Creation Failed!", "Error!",  MB_ICONEXCLAMATION | MB_OK);   return 0;    }   ShowWindow(hwnd, nCmdShow);    UpdateWindow(hwnd);   // Step 3: The Message Loop    while(GetMessage(&Msg, NULL, 0, 0) > 0)    {   TranslateMessage(&Msg);   DispatchMessage(&Msg);    }    return Msg.wParam;}LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam){   switch(msg)    {   case WM_CREATE:   { // Create GUI   RECT clientR;   GetClientRect(hwnd,&clientR);   createGUI(hwnd,gInstance,clientR);   }  break;   case WM_LBUTTONDOWN:   {   RECT clientR;   GetClientRect(hwnd,&clientR);   POINT cursor;   cursor.x = LOWORD(lParam); cursor.y = HIWORD(lParam); // if mouse has clicked INSIDE client area   int collision = PtInRect(&clientR,cursor);   if (collision != 0) {    myLaser.createTarget(hwnd,LOWORD(lParam),HIWORD(lParam));  myLaser.lButtonDown = true; }   }   break;   case WM_LBUTTONUP:   {   myLaser.lButtonDown = false;   }   break;   case WM_KEYDOWN:   {   switch(LOWORD(wParam)) {  case VK_CONTROL:   {    // Just to show problem with drawing laser   // it is not drawn long enough   if (myLaser.sleepValue == 200)   {  myLaser.sleepValue = 0;   }   else myLaser.sleepValue = 200; MessageBox(hwnd,"Problem with drawing laser should occur now","",MB_OK);   }   break;   default:   break;   }   }   break;   case WM_COMMAND:   {   switch(LOWORD(wParam)) {  case LA_DRAWLASER:   {    // Fire laser at coordinates   // if laser has a target to shoot at & we are not already shooting   if (myLaser.hasTarget() && !myLaser.drawNewLaser) {    myLaser.drawNewLaser = true;  myLaser.initAnimationTimer(hwnd);  InvalidateRect(hwnd,NULL,false);  UpdateWindow(hwnd); } SetFocus(hwnd);   }   break;  default:   break;   }   }   break;   case WM_TIMER:   { switch(LOWORD(wParam)) {  case LA_TIMER:   {    // Laser can create another target now myLaser.ceaseDelay(hwnd); // if left mouse button is still held down   if (myLaser.lButtonDown) {  RECT clientR; GetClientRect(hwnd,&clientR); POINT cursor; GetCursorPos(&cursor); ScreenToClient(hwnd,&cursor);  // if mouse is INSIDE client area int collision = PtInRect(&clientR,cursor); if (collision != 0) {myLaser.createTarget(hwnd,cursor.x,cursor.y);  }   }   }    break;    case LA_ANIMATION:   {    // Draw new laser position   if (myLaser.hasTarget()) {    InvalidateRect(hwnd,NULL,false);  UpdateWindow(hwnd);}   else myLaser.ceaseAnimationTimer(hwnd);   }    break; default:   break;   }   }    break;   case WM_PAINT:   {   HDC hdc;   PAINTSTRUCT ps;   hdc = BeginPaint(hwnd,&ps); myLaser.drawController(hdc,hwnd); EndPaint(hwnd,&ps);   }   break;   case WM_ERASEBKGND://return 1;   break;   case WM_CLOSE:   {  myLaser.ceaseDelay(hwnd);  myLaser.ceaseAnimationTimer(hwnd);  //myLaser.~laser();  DestroyWindow(hwnd);   }   break;   case WM_DESTROY:  PostQuitMessage(0);   break;   default:    break;    }    return DefWindowProc(hwnd, msg, wParam, lParam);}
Quote:Is my method of creating the look of movement or animation the best way that can be done in Win32. Is there a completely different, better, more efficient method you can suggest? Or is my way good but maybe I could imporve with 'blah' tweaks?Search for "double buffering" and "dirty rectangles" for more generic approaches that would allow you to build a simple GDI-based game engine. The dirty rectangles method works well for GDI since it does minimum redrawing. And you could change it to a "dirty regions" approach to get what you need for this app.To get rid of the flickering, here's one inefficient way that uses a memory bitmap as a temporary off-screen buffer to combine the two steps of the laser redrawing into one. This is an example of double buffering. Ideally your memory bitmap would be the size of your window and you would only draw to it, and then use a "dirty regions" list to tell your paint message handler which parts of the memory bitmap need to be updated to the window. In laser::drawLaser, make the following code mods:- comment out InvalidateRgn call line- comment out CreatePolygonRgn call line- comment out FillRgn call line- add the following code after the FillRgn call line:HRGN newRgn = CreatePolygonRgn(laserPnts,4,WINDING); // laser's new lookRgnRedraw(hwnd, hdc, objRgn, newRgn); // "undraw" old and draw newDeleteObject(objRgn); // always delete or destroy "Create"d GDI objectsobjRgn = newRgn;Add the following new function somewhere above laser::drawLaser:void RgnRedraw(HWND hWnd, HDC hDC, HRGN hRgnOld, HRGN hRgnNew,bool buffered=true, COLORREF color=RGB(0, 0, 0)){    if( !buffered )    {   InvalidateRgn(hWnd, hRgnOld, TRUE);   HBRUSH hBrush = CreateSolidBrush(color);   FillRgn(hDC, hRgnNew, hBrush);   DeleteObject(hBrush);   return;    }    // Step 1: Setup    RECT rOld, rNew, rBlt;    GetRgnBox(hRgnOld, &rOld); // bounding box for the region's old look    GetRgnBox(hRgnNew, &rNew); // bounding box for the region's new look    UnionRect(&rBlt, &rOld, &rNew); // minimum rect containing the two regions    int bw = rBlt.right  - rBlt.left; // that rect's width    int bh = rBlt.bottom - rBlt.top;  // that rect's height    HDC  hMC    = CreateCompatibleDC(hDC);    HBITMAP   hBlt   = CreateCompatibleBitmap(hDC, bw, bh);    ULONG_PTR pBFill = GetClassLongPtr(hWnd, GCLP_HBRBACKGROUND);    HGDIOBJ   hMCBmp = SelectObject(hMC, hBlt);    // Step 2: Get a copy of the pixels that will be modified    BitBlt(hMC, 0, 0, bw, bh, hDC, rBlt.left, rBlt.top, SRCCOPY);    // Step 3: Get rid of the region's old look    //    Equivalent to the non-buffered method's InvalidateRgn call    // translate region to the memory device context's coord system    OffsetRgn(hRgnOld, -rBlt.left, -rBlt.top);    // don't modify any pixels outside of the region    SelectClipRgn(hMC, hRgnOld);    if( pBFill == 0 )    {   // NOTE: assumes the following about the window's "erase background"   //  message handler:   //  - it will use the DC passed to it (which the Windows default   //    message processor should do)   //  - it will not call this function directly or call a function   //    that indirectly   //    results in a call to this function (which would cause an   //    infinite loop)   SendMessage(hWnd, WM_ERASEBKGND, (WPARAM)hMC, 0);    }    else    {   // assumes no system color indices > 16 bits   HBRUSH hBFill = pBFill <= 65536 ? GetSysColorBrush((int)pBFill-1) : (HBRUSH)pBFill;   FillRgn(hMC, hRgnOld, hBFill);    }    OffsetRgn(hRgnOld, rBlt.left, rBlt.top);    SelectClipRgn(hMC, NULL);    // Step 4: Put in the region's new look    //    Equivalent to the non-buffered method's FillRgn call    HBRUSH hBrush = CreateSolidBrush(color);    OffsetRgn(hRgnNew, -rBlt.left, -rBlt.top);    FillRgn(hMC, hRgnNew, hBrush);    OffsetRgn(hRgnNew,  rBlt.left,  rBlt.top);    DeleteObject(hBrush);    // Step 5: Put the modified pixels back    BitBlt(hDC, rBlt.left, rBlt.top, bw, bh, hMC, 0, 0, SRCCOPY);    // Step 6: Cleanup    SelectObject(hMC, hMCBmp);    DeleteObject(hBlt);    DeleteDC(hMC);}