Hello
I 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_TIMER 50001
#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);
#endif
Object 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 false
if (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);
}