WinApi LineTo not refreshing the line - c++

I'm just starting learning C++. All I want to do is drawing a line to a specified coord, which comes as an input in a method. I set the starting point in each loop (calling this function in a loop with different arguments) using MoveToEx and giving the coordinates in which I want lane to be drawn.
Any ideas how to make it working in a loop?
My code is similar to:
void Clock::drawSecondLine(float x,float y) {
HWND console_handle = GetConsoleWindow();
HDC device_context = GetDC(console_handle);
HPEN pen = CreatePen(PS_SOLID, 3, RGB(255, 0, 0));
SelectObject(device_context, pen);
MoveToEx(device_context, 0, 0, NULL);
Ellipse(device_context, 400, 0, 0, 400);
MoveToEx(device_context, 200, 200, NULL);
LineTo(device_context, (int)x, (int)y);
ReleaseDC(console_handle, device_context);
cin.ignore();
}
And the loop:
void Zegar::startClock() {
while (true) {
drawSecondLine(laneShowingSecond.getX(), laneShowingSecond.getY());
laneShowingSecond.movePointByRadius(RADIUS_PER_SECOND);
Sleep(1000);
increaseSecond();
}
}

Here's some sample code (which I am running in VStudio 2k10).
Notes:
Although it is compiled as C++ (and uses some C++ features like iostream - meaning that it won't compile as C), it's still plain old C.
It has a lot of ugly stuff and NO-NO s (like global vars, lots defines, mixture of C and C++ code, drawing on the console window, ...). The goal was to have a PoC; the code can be cleansed later.
From the bounding rectangle (RECT_* defines (400, 0, 0, 400)) I am extracting the center coordinates, and the radii on the X and Y axes, using some simple mathematical calculations (due to the fact that the rectangle is a square, the 2 radii are equal so we hit the particular case where the ellipse is actually a circle).
nextPoint function is the replacement of laneShowingSecond.getX(), laneShowingSecond.getY() from your code.
Everything that needs to be only executed once (at the beginning) is placed in the init function. Note that if something goes wrong during initialization, it exits with an error (< 0) code, since it won't be able to draw.
Similarily, any cleanup stuff to be performed at the end is placed in the cleanup function (here I didn't bother to check for return codes since it's exiting anyway).
The draw function contains the drawing loop. In every iteration:
The angle is incremented by INCERMENT_DEG (which by default is 30°).
The next point on the ellipse is calculated using some simple trigonometrical formulas.
The drawing is performed.
A wait of ITERATION_SLEEP_TIME milliseconds (I've set it to 200 to avoid waiting one second for each line drawn) takes place.
Note that:
It stops after reaching 360° or 2 * PI Radians (round-the-clock), because it doesn't make sense to draw the same lines over and over again.
The drawing is performed counter-clockwise (positive angles in trigonometry); also the xOy origin (O(0, 0)) is screen's upper left corner.
Play with the defines that I marked with comments, by assigning various values to them and see how the drawing changes.
main.cpp:
#include <iostream>
#define _USE_MATH_DEFINES
#include <math.h>
#include <Windows.h>
#define RECT_LEFT 400 // Modify any of these 4 RECT_* values to get different ellipse shapes.
#define RECT_TOP 0
#define RECT_RIGHT 0
#define RECT_BOT 400
#define ITERATION_SLEEP_TIME 200 // Sleep time while in loop.
#define INCERMENT_DEG 30 // 30 degrees per step; a full circle has 360 (2 * PI RAD).
#define M_PI_180 M_PI / 180
using std::cout;
using std::endl;
typedef enum {DRAW_RADII, DRAW_POLY} DrawMethod;
const int radiusX = abs(RECT_RIGHT - RECT_LEFT) / 2;
const int radiusY = abs(RECT_BOT - RECT_TOP) / 2;
const int centerX = (RECT_RIGHT + RECT_LEFT) / 2;
const int centerY = (RECT_BOT + RECT_TOP) / 2;
HWND hwnd = NULL;
HDC hdc = NULL;
HPEN hpen = NULL;
DrawMethod meth = DRAW_RADII; // Modify this to DRAW_POLY to draw a polygon instead of the "bike wheel".
int deg = 0;
double x = 0, y = 0;
void nextPoint(int degree, double *x, double *y) {
*x = centerX + radiusX * cos(M_PI_180 * degree );
*y = centerY - radiusY * sin(M_PI_180 * degree);
}
int init() {
if ((hwnd = GetConsoleWindow()) == NULL) {
cout << "GetConsoleWindow error: " << GetLastError() << endl;
return -1;
}
if ((hdc = GetDC(hwnd)) == NULL) {
cout << "GetDC error: " << GetLastError() << endl;
return -2;
}
if ((hpen = CreatePen(PS_SOLID, 3, RGB(255, 0, 0))) == NULL) {
cout << "CreatePen error: " << GetLastError() << endl;
return -3;
}
SelectObject(hdc, hpen);
Ellipse(hdc, RECT_LEFT, RECT_TOP, RECT_RIGHT, RECT_BOT);
nextPoint(deg, &x, &y);
if (meth == DRAW_RADII) {
MoveToEx(hdc, centerX, centerY, NULL);
LineTo(hdc, (int)x, (int)y);
} else if (meth == DRAW_POLY) {
MoveToEx(hdc, (int)x, (int)y, NULL);
}
return 0;
}
void draw() {
while (deg < 360) {
deg += INCERMENT_DEG;
nextPoint(deg, &x, &y);
if (meth == DRAW_RADII) {
MoveToEx(hdc, centerX, centerY, NULL);
LineTo(hdc, (int)x, (int)y);
} else if (meth == DRAW_POLY) {
LineTo(hdc, (int)x, (int)y);
} else
break;
Sleep(ITERATION_SLEEP_TIME);
}
}
void cleanup() {
if (hpen) {
DeleteObject(hpen);
}
if (hwnd && hdc) {
ReleaseDC(hwnd, hdc);
}
}
int main() {
if (!init())
draw();
cleanup();
return 0;
}

Related

Convert Gdiplus::Region to ID2D1Geometry* for clipping

I am trying to migrate my graphics interface project from Gdiplus to Direct2D.
Currently, I have a code that calculates clipping area for an rendering object:
Graphics g(hdc);
Region regC = Rect(x, y, cx + padding[2] + padding[0], cy + padding[3] + padding[1]);
RecursRegPos(this->parent, &regC);
RecursRegClip(this->parent, &regC);
g.setClip(g);
...
inline void RecursRegClip(Object *parent, Region* reg)
{
if (parent == CARD_PARENT)
return;
if (parent->overflow != OVISIBLE)
{
Rect regC(parent->getAbsoluteX(), parent->getAbsoluteY(), parent->getCx(), parent->getCy()); // Implementation of these function is not necceassary
GraphicsPath path;
path.Reset();
GetRoundRectPath(&path, regC, parent->borderRadius[0]);
// source https://stackoverflow.com/a/71431813/15220214, but if diameter is zero, just plain rect is returned
reg->Intersect(&path);
}
RecursRegClip(parent->parent, reg);
}
inline void RecursRegPos(Object* parent, Rect* reg)
{
if (parent == CARD_PARENT)
return;
reg->X += parent->getX() + parent->padding[0];
reg->Y += parent->getY() + parent->padding[1];
if (parent->overflow == OSCROLL || parent->overflow == OSCROLLH)
{
reg->X -= parent->scrollLeft;
reg->Y -= parent->scrollTop;
}
RecursRegPos(parent->parent, reg);
}
And now I need to convert it to Direct2D. As You may notice, there is no need to create Graphics object to get complete calculated clipping region, so I it would be cool if there is way to just convert Region to ID2D1Geometry*, that, as far, as I understand from msdn article need to create clipping layer.
Also, there is probably way to convert existing code (RecursRegClip, RecursRegPos) to Direct2D, but I am facing problems, because I need to work with path, but current functions get region as an argument.
Update 1
There is Region::GetData method that returns, as I understand array of points, so maybe there is possibility to define either ID2D1PathGeometry or ID2D1GeometrySink by points?
Update 2
Oh, maybe
ID2D1GeometrySink::AddLines(const D2D1_POINT_2F *points, UINT32 pointsCount)
is what do I need?
Unfortunately, GetData of region based on just (0,0,4,4) rectangle returns 36 mystique values:
Region reg(Rect(0, 0, 4, 4));
auto so = reg.GetDataSize();
BYTE* are = new BYTE[so];
UINT fi = 0;
reg.GetData(are, so, &fi);
wchar_t ou[1024]=L"\0";
for (int i = 0; i < fi; i++)
{
wchar_t buf[10] = L"";
_itow_s(are[i], buf, 10, 10);
wcscat_s(ou, 1024, buf);
wcscat_s(ou, 1024, L", ");
}
// ou - 28, 0, 0, 0, 188, 90, 187, 128, 2, 16, 192, 219, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 64, 0, 0, 128, 64,
I rewrote the solution completely, it seems to be working:
// zclip is ID2D1PathGeometry*
inline void Render(ID2D1HwndRenderTarget *target)
{
ID2D1RoundedRectangleGeometry* mask = nullptr;
ID2D1Layer* clip = nullptr;
if(ONE_OF_PARENTS_CLIPS_THIS || THIS_HAS_BORDER_RADIUS)
{
Region reg = Rect(x, y, cx + padding[2] + padding[0], cy + padding[3] + padding[1]);
RecursRegPos(this->parent, &reg);
D2D1_ROUNDED_RECT maskRect;
maskRect.rect.left = reg.X;
maskRect.rect.top = reg.Y;
maskRect.rect.right = reg.X + reg.Width;
maskRect.rect.bottom = reg.Y + reg.Height;
maskRect.radiusX = this->borderRadius[0];
maskRect.radiusY = this->borderRadius[1];
factory->CreateRoundedRectangleGeometry(maskRect, &mask);
RecursGeoClip(this->parent, mask);
target->CreateLayer(NULL, &clip);
if(zclip)
target->PushLayer(D2D1::LayerParameters(D2D1::InfiniteRect(), zclip), clip);
else
target->PushLayer(D2D1::LayerParameters(D2D1::InfiniteRect(), mask), clip);
SafeRelease(&mask);
}
// Draw stuff here
if (clip)
{
target->PopLayer();
SafeRelease(&clip);
SafeRelease(&mask);
SafeRelease(&zclip);
}
}
...
inline void RecursGeoClip(Object* parent, ID2D1Geometry* geo)
{
if (parent == CARD_PARENT)
return;
ID2D1RoundedRectangleGeometry* maskParent = nullptr;
if (parent->overflow != OVISIBLE)
{
Rect regC(parent->getAbsoluteX(), parent->getAbsoluteY(), parent->getCx(), parent->getCy());
ID2D1GeometrySink* sink = nullptr;
ID2D1PathGeometry* path = nullptr;
SafeRelease(&path);
factory->CreatePathGeometry(&path);
D2D1_ROUNDED_RECT maskRect;
maskRect.rect.left = regC.X;
maskRect.rect.top = regC.Y;
maskRect.rect.right = regC.X + regC.Width;
maskRect.rect.bottom = regC.Y + regC.Height;
maskRect.radiusX = parent->borderRadius[0];
maskRect.radiusY = parent->borderRadius[1];
path->Open(&sink);
factory->CreateRoundedRectangleGeometry(maskRect, &maskParent);
geo->CombineWithGeometry(maskParent, D2D1_COMBINE_MODE_INTERSECT, NULL, sink);
sink->Close();
SafeRelease(&sink);
SafeRelease(&this->zclip);
this->zclip = path;
RecursGeoClip(parent->parent, this->zclip);
}
else
RecursGeoClip(parent->parent, geo);
SafeRelease(&maskParent);
}
Now I can enjoy drawing one image and two rectangles in more than 60 fps, instead of 27 (in case of 200x200 image size, higher size - lower fps) with Gdi+ -_- :

in Vsual Studio C++ OffsetRgn not working>

void CBallBounceView::OnDraw(CDC* pDC)
{
CBallBounceDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
CString str;
str.Format(_T("Counter = %d"), counter);
CRgn rgnA,copyeed;
VERIFY(rgnA.CreateEllipticRgn(50, 50, 150, 150));
CBrush brA;
VERIFY(brA.CreateSolidBrush(RGB(255, 0, 0)));
VERIFY(pDC->FillRgn(&rgnA, &brA)); // rgnA Red Filled
int nOffsetResult = rgnA.OffsetRgn(500, 100);
ASSERT(nOffsetResult != ERROR && nOffsetResult != NULLREGION);
}
I am trying to offset the ellipse region but it is not offsetting. I am new in visual studio C++;
I am using Visual studio 2019.
The problem is that, once output has been rendered to the given device context (as by your pDC->FillRgn(&rgnA, &brA) call), any subsequent changes to that region will not change the displayed output.
In order to do this, you will need to perform a cycle of operations:
Draw the object in its original position.
Then, when moved, erase that original object; and...
Draw the object in its new position.
Repeat each time you move the object.
You typically perform the 'erase' operation by drawing the object each time using the XOR (exclusive or) 'mix mode' on the target device context. Thus, the first time you draw this, it will paint the object over the background, and the second time, it will restore that background. You can use the CDC::SetROP2(R2_XORPEN) function to set that mode.
Here's a 'scruffy' (but runnable) program demonstrating the use of this XOR drawing technique. It uses 'raw' Windows GDI calls (rather than MFC) and is a Console-Mode program, but it will hopefully show the basic principles of the technique:
#include <Windows.h>
#define BLOBSIZE 20
#define MOVESTEP 5
int main()
{
SetProcessDPIAware(); // Comment this line out if using 'older' versions of Windows.
HWND hWnd = GetConsoleWindow();
HDC hDC = GetDC(hWnd);
RECT rc; GetClientRect(hWnd, &rc);
int x = 50, y = 30;
int dx = MOVESTEP, dy = MOVESTEP;
SetROP2(hDC, R2_XORPEN);
SelectObject(hDC, GetStockObject(WHITE_BRUSH));
SelectObject(hDC, GetStockObject(NULL_PEN));
do {
Ellipse(hDC, x, y, x + BLOBSIZE, y + BLOBSIZE); // First call: Draws the object
Sleep(10); // Here, we just wait, but you can do whatever else you want between moves
Ellipse(hDC, x, y, x + BLOBSIZE, y + BLOBSIZE); // Second call: erase it
x += dx;
if ((dx > 0 && x >= rc.right - BLOBSIZE) || (dx < 0 && x == 0)) {
Beep(1000, 10);
dx *= -1;
}
y += dy;
if ((dy > 0 && y >= rc.bottom - BLOBSIZE) || (dy < 0 && y == 0)) {
Beep(1000, 10);
dy *= -1;
}
} while (!(GetAsyncKeyState(VK_SPACE) & 0x8000)); // Press spacebar to quit!
ReleaseDC(hWnd, hDC);
return 0;
}

Win32 GDI: AlphaBlend() not using constant alpha value correctly

The code provided at the end draws a grid of red 3x3px rectangles with a random constant alpha value using AlphaBlend(). The output however, turns out not "quite" random:
Notice runs of constant alpha along x-axis.
What might be causing this?
P.S. Stepping though the debugger produces the expected output.
Code to produce output:
void draw_mark(HDC hdc, int x, int y,
COLORREF mark_clr, int mark_w, int mark_h, BYTE alpha);
void produce_output(HWND hWnd) {
InvalidateRect(hWnd, NULL, TRUE);
UpdateWindow(hWnd);
const int grid_w = 64, grid_h = 64;
const int mark_sz = 3;
HDC hdc = GetDC(hWnd);
for(int y = 0; y < grid_h; ++y) {
for(int x = 0; x < grid_w; ++x) {
BYTE rnd_alpha = rand(); // use random alpha for each mark
draw_mark(hdc, x * mark_sz, y * mark_sz,
RGB(255,0,0), mark_sz, mark_sz, rnd_alpha);
}
}
// clean-up
ReleaseDC(hWnd, hdc);
}
// draws a [mark_w x mark_h] rectangle at (x,y) with alpha
void draw_mark(HDC hdc, int x, int y,
COLORREF mark_clr, int mark_w, int mark_h, BYTE alpha)
{
HDC hdcMem = CreateCompatibleDC(NULL);
HBITMAP hbm = CreateCompatibleBitmap(hdc, mark_w, mark_h);
HGDIOBJ hOldBmp = SelectObject(hdcMem, hbm);
for(int x = 0; x < mark_w; ++x) {
for(int y = 0; y < mark_h; ++y) {
SetPixel(hdcMem, x, y, mark_clr);
}
}
POINT marker_center{mark_w / 2, mark_h / 2};
SetPixel(hdcMem, marker_center.x, marker_center.y, RGB(255, 255, 255));
BLENDFUNCTION bf{};
bf.BlendOp = AC_SRC_OVER;
bf.BlendFlags = 0;
bf.AlphaFormat = 0; // ignore source per-pixel alpha and...
bf.SourceConstantAlpha = alpha; // ...use constant alpha provided instead
AlphaBlend(hdc,
x - marker_center.x, y - marker_center.y,
mark_w, mark_h,
hdcMem, 0, 0, mark_w, mark_h, bf);
// clean-up
SelectObject(hdcMem, hOldBmp);
DeleteObject(hbm);
DeleteDC(hdcMem);
};
EDIT - As I look more into it, here are the additional issues I have noticed:
1- Output is normal when AlphaBlend() destination is a memory DC, but not when a window DC. So the issue has to do with bliting directly to screen.
2- Corrupt output is unrelated to use of rand() function. Replacing BYTE rnd_alpha = rand(); with ++alpha also produces somewhat similar corrupt outputs.
3- More interestingly, suspending the thread in the inner loop such as Sleep(some_duration) seems to reduce the corruption. Higher the some_duration, less the corruption. Here is a sample output:
First output is generated by first blitting to a memory DC, then to window. The rest is directly to the window. Notice how corruption increases(i.e. output becomes less random) as thread suspension time decreases.

GetPixel() not working correctly Windows API C++

I'm writing a program that reads each pixel of a window and store it in an array of bytes as black and white, each bit of the bytes is a black/white value.
But GetPixel() doesn't seem to work the way I expected. Here's the part of the code for reading pixels and storing them:
byte *colors = new byte[250000 / 8 + 1];
ZeroMemory(colors, 250000 / 8 + 1);
HDC hdc = GetDC(hwnd);
HDC memDC = CreateCompatibleDC(hdc);
HBITMAP memBitmap = CreateCompatibleBitmap(hdc, 500, 500);
SelectObject(memDC, memBitmap);
BitBlt(memDC, 0, 0, 500, 500, hdc, 0, 0, SRCCOPY);
for (int y = 0; y < 500; y++) {
for (int x = 0; x < 500; x++) {
COLORREF pxcolor = GetPixel(memDC, x, y);
if (pxcolor == CLR_INVALID) {
MessageBox(hwnd, _T("Oops..."), NULL, NULL);
}
int r = GetRValue(pxcolor);
int g = GetGValue(pxcolor);
int b = GetBValue(pxcolor);
int average = (r + g + b) / 3;
bool colorBW = average >= 128;
int currentIndex = y * 500 + x;
if (colorBW) {
SetBit(colors, currentIndex);
}
}
}
ReleaseDC(hwnd, hdc);
DeleteDC(memDC);
DeleteObject(memBitmap);
delete[] colors;
SetBit():
inline VOID SetBit(byte *bytes, int index, bool state = true) {
byte byteToSet = bytes[index / 8];
int bitNumber = index % 8;
bytes[index / 8] = state ? (byteToSet | (0b1000'0000 >> bitNumber)) : (byteToSet & ((0b1111'1111 >> (bitNumber + 1)) | (0b1111'1111 << (8 - bitNumber - 1))));
}
Every pixel read in by GetPixel() seems to give me 0x000000, or pure black.
My code used to call GetPixel() with the first parameter being hdc, without all the bitmap and memory DC stuff, but that way every pixel returns CLR_INVALID. I came across this question, and the above code is after I have changed it into using memory DCs and bitmaps. But it just went from returning CLR_INVALID to 0x000000 for each pixel.
If I add this line before I use GetPixel():
SetPixel(memDC, x, y, RGB(255, 255, 255));
GetPixel() returns the correct result. Why is it functioning this way?

Inconsistent Behaviour Between 2 Players of Game

I've been making a game for an English presentation (I know right?) and I've been having a few weird problems recently.
At this stage, there are two squares for the players that can fire bullets. When the bullets hit the side of the screen or the middle border, they should disappear and possibly be reused later.
The problem is, first of all, when either player shoots up or down, the bullets disappear into the top/bottom/middle of the screen like they're supposed to. If player 2 shoots a few to the side, as soon as one hits, they seem to all disappear (only player 2's bullets) and I have to wait a few seconds before it starts shooting again.
The main problem though, is that even when I use the exact same code modified for player 1 instead of player 2, when player 1's first shot to hit the side of the screen gets there, it segfaults the program.
The REALLY odd thing is that at one point, this happened, and without changing anything, I ran it again and it all worked perfectly fine. It might have to do with the order of bullets between player 1 and 2, or even where I shoot first. I've tried recreating it, but no results yet.
Just a note before you try to compile the code, I used a wrapper I made to make the window with ease. I noted down what goes on behind the scenes with /// comments though, so adding that info into whatever method you use to make your windows will work just as well.
Problem areas are listed near the bottom:
///Works best on 1280x1024 resolution
///1 vs 1 splitscreen game that involves flying around and shooting things
///angles start at 0 facing upwards and increase clockwise
#include <window.h> //incomplete wrapper, but works perfectly for quick, easy window creation
#define _USE_MATH_DEFINES //for M_PI
#include <cmath> //for M_PI
#include <iostream> //used for debugging
using std::cout; //output
struct Actions //actions a player can take
{
bool up; //if player is moving in these 4 directions, they will be true
bool left;
bool down;
bool right;
bool shoot; //if player is shooting, this will be true
};
struct Player //a player
{
Player() {};
void fire(); //fire a bullet
void checkActions (HWND); //check what actions player is taking
double x; //position (centre of square)
double y;
double angle; //angle (might add diagonals so...)
int pnum; //player number (0 or 1)
COLORREF colour; //player's colour
Actions action; //player's actions
};
struct Bullet //a bullet
{
double x; //position (centre of square)
double y;
Player owner; //owner of bullet
int index; //bullet's index in array
double angle; //bullet's angle
};
Player *p = new Player[2]; //2 players
Bullet **bullet; //2d array of bullets
int bcount[2] = {0}; //number of bullets for each player
int btime [2] = {0}; //timer for bullets
const double PLSIZE = 10; //player size = 20x20 square (10 from centre outwards)
const double BSIZE = 2; //bullet size = 4x4 square
const double SPEED = 1; //player's moving speed is 1
const int BDELAY = 100; //delay between bullets is 100ms
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM); //window procedure
void OnPaint (HDC, HWND); //painting function
void moveBullets (HWND); //calculates bullet positions
void deleteBullet (int, int); //"deletes" a bullet
int main() //main function
{
//hide(console()); //hides console window (currently showing for debugging)
bullet = new Bullet*[2]; //create bullet array of 1000/player (I'll size down the 1000 later)
bullet[0] = new Bullet[1000];
bullet[1] = new Bullet[1000];
p[0].x = 630; //player 1's position
p[0].y = 250;
p[0].colour = RGB(255,0,0); //player 1 is red
p[0].pnum = 0; //player 1's number is 0
p[0].angle = 0; //face upwards
p[0].action = {0}; //player 1 is doing nothing
p[1].x = 630; //player 2's position
p[1].y = 750;
p[1].colour = RGB(0,0,255); //player 2 is blue
p[1].pnum = 1; //player 2's number is 1
p[1].angle = 0; //face upwards
p[1].action = {0}; //player 2 is doing nothing
Window window; //create window object (part of wrapper, sets default values for class and window)
///background = (HBRUSH)COLOR_WINDOW
///class name = "Default Wrapper Class"
///hInstance = GetModuleHandle (NULL)
///all others are standard default or 0
window.createClass(WndProc); //create class using earlier-mentioned window procedure
window.setStyle(WS_OVERLAPPEDWINDOW | WS_MAXIMIZE); //set window style to overlapped and maximized
window.setTitle (L"Word Blaster"); //set window title to "Word Blaster" (it's an English project, shush)
///x/y/width/height = CW_USEDEFAULT
///class name = other class name
///hInstance = GetModuleHandle (NULL)
///all others are standard default or 0
HWND hwnd = window.createWindow(); //create window
MSG msg; //message loop
while(GetMessage(&msg,0,0,0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) //window proc
{
HDC hdc; //hdc for painting
PAINTSTRUCT ps; //paintstruct for painting
bool ret = false; //return value (you'll see later)
switch(msg)
{
case WM_CREATE: //currently not in use
break;
case WM_KEYDOWN: //check for pressed keys
switch (wParam) //keycode
{
case 0x57: //'w'
p[0].action.up = true; //player 1 wants to move up (couldn't just change position here or no diagonal movement)
break;
case 0x41: //'a', left
p[0].action.left = true;
break;
case 0x53: //'s', down
p[0].action.down = true;
break;
case 0x44: //'d', right
p[0].action.right = true;
break;
case 0x20: // space, shoot
p[0].action.shoot = true;
break;
case VK_UP: //up arrow, player 2 up
p[1].action.up = true;
break;
case VK_LEFT: //left arrow
p[1].action.left = true;
break;
case VK_DOWN: //down arrow
p[1].action.down = true;
break;
case VK_RIGHT: //right arrow
p[1].action.right = true;
break;
case VK_RETURN: //either enter key, p2 shoot
p[1].action.shoot = true;
break;
}
break;
case WM_KEYUP: //check for unpressed keys
switch (wParam)
{
case 0x57: //'w', player 1 should stop moving up
p[0].action.up = false;
break;
case 0x41: //all same order as above
p[0].action.left = false;
break;
case 0x53:
p[0].action.down = false;
break;
case 0x44:
p[0].action.right = false;
break;
case 0x20: // space
p[0].action.shoot = false;
break;
case VK_UP:
p[1].action.up = false;
break;
case VK_LEFT:
p[1].action.left = false;
break;
case VK_DOWN:
p[1].action.down = false;
break;
case VK_RIGHT:
p[1].action.right = false;
break;
case VK_RETURN:
p[1].action.shoot = false;
break;
}
break;
case WM_PAINT: //draw on screen
hdc = BeginPaint (hwnd, &ps); //prepare window for drawing
OnPaint (hdc,hwnd); //draw
EndPaint (hwnd, &ps); //finish drawing
break;
case WM_CLOSE: //if ready to close
show(console()); //show console window in case it doesn't close
end(); //close console window (PostMessage (GetConsoleWindow(),WM_CLOSE,0,0))
DestroyWindow(hwnd); //close main window
break;
case WM_DESTROY: //window is closing
PostQuitMessage(0); //post WM_QUIT to end (probably won't get here since console closes earlier)
break;
case WM_ERASEBKGND: //if background is going to be erased, don't let it (causes flicker)
ret = true; //hold that thought for a bit
break;
}
p[0].checkActions(hwnd); //check player 1's actions
p[1].checkActions(hwnd); //check player 2's actions
moveBullets (hwnd); //move any bullets
InvalidateRect (hwnd,NULL,true); //update window
Sleep (1); //delay a bit
if (!ret) return DefWindowProc(hwnd, msg, wParam, lParam); //if WM_ERASEBKGND wasn't called, take default action
}
void Player::fire() //fire a bullet
{
bullet [pnum][bcount[pnum]].x = x; //bullet starts in player's centre
bullet [pnum][bcount[pnum]].y = y;
bullet [pnum][bcount[pnum]].owner = *this; //owner of bullet is the object calling this function
bullet [pnum][bcount[pnum]].index = bcount[pnum]; //index of bullet is the number of bullets for player
bullet [pnum][bcount[pnum]].angle = angle; //angle of bullet is player's angle
while (
(bullet[pnum][bcount[pnum]].x - BSIZE < x + PLSIZE && bullet[pnum][bcount[pnum]].x - BSIZE > x - PLSIZE //left side of bullet inside player OR
|| bullet[pnum][bcount[pnum]].x + BSIZE < x + PLSIZE && bullet[pnum][bcount[pnum]].x + BSIZE > x - PLSIZE) //right side in player --- AND ---
&& (bullet[pnum][bcount[pnum]].y - BSIZE < y + PLSIZE && bullet[pnum][bcount[pnum]].y - BSIZE > y - PLSIZE //top in player OR
|| bullet[pnum][bcount[pnum]].y + BSIZE < y + PLSIZE && bullet[pnum][bcount[pnum]].y + BSIZE > y - PLSIZE) //bottom in player
)
{
bullet[pnum][bcount[pnum]].x += sin (bullet[pnum][bcount[pnum]].angle * M_PI / 180); //start moving bullet until it's out
bullet[pnum][bcount[pnum]].y -= cos (bullet[pnum][bcount[pnum]].angle * M_PI / 180);
}
btime [pnum] = GetTickCount(); //set up bullet delay for that player
++bcount[pnum]; //increase number of bullets for that player
}
void Player::checkActions (HWND hwnd) //check player's actions
{
RECT r;
GetClientRect (hwnd, &r); //get canvas space
if (action.up) //if moving up
{
y -= SPEED; //change y position
angle = 0; //change angle
if (pnum == 0) //if player 1
{
if (y - PLSIZE < 1) y = PLSIZE + 1; //check top of screen boundary
}
else //if player 2
{
if (y - PLSIZE < r.bottom / 2 + 5) y = r.bottom / 2 + 5 + PLSIZE; //check middle boundary
}
}
if (action.left) //if moving left
{
x -= SPEED; //change x position
angle = 270; //change angle
if (x - PLSIZE < 1) x = PLSIZE + 1; //check left of screen boundary
}
if (action.down) //down is opposite of up
{
y += SPEED;
angle = 180;
if (pnum == 0)
{
if (y + PLSIZE > r.bottom / 2 - 5) y = r.bottom / 2 - 5 - PLSIZE;
}
else
{
if (y + PLSIZE > r.bottom) y = r.bottom - PLSIZE;
}
}
if (action.right) //right is opposite of left
{
x += SPEED;
angle = 90;
if (x + PLSIZE > r.right) x = r.right - PLSIZE;
}
if (action.shoot && GetTickCount() - btime [pnum] > BDELAY) fire(); //if player wants to shoot and enough time has passed, fire bullet
}
void OnPaint (HDC hdc, HWND hwnd) //draw stuff
{
RECT r;
GetClientRect (hwnd, &r); //get canvas area
HDC buffer = CreateCompatibleDC (hdc); //create buffer DC
HBITMAP bitmap = CreateCompatibleBitmap (hdc,r.right,r.bottom); //create buffer bitmap
HBITMAP oldBM = (HBITMAP)SelectObject (buffer, bitmap); //create another bitmap
HBRUSH player1brush = CreateSolidBrush(p[0].colour); //player 1's brush
HBRUSH player2brush = CreateSolidBrush(p[1].colour); //player 2's brush
HBRUSH blackBrush = CreateSolidBrush (RGB(0,0,0)); //black brush
HPEN /*player1*/pen = CreatePen (PS_NULL,1,RGB(255,0,0)); //don't need pen
BitBlt(buffer,0,0,r.right,r.bottom,NULL,0,0,WHITENESS); //erase bitmap background
SelectObject(buffer,pen); //select pen (since I need one to do anything)
SelectObject (buffer, blackBrush); //select black brush
Rectangle (buffer, 0, r.bottom / 2 - 5, r.right, r.bottom / 2 + 5); //draw middle line
// MoveTo () //these comments are because I was about to change the graphics to ships
SelectObject (buffer,player1brush); //select player 1's brush
Rectangle (buffer,p[0].x-PLSIZE,p[0].y-PLSIZE,p[0].x+PLSIZE,p[0].y+PLSIZE); //draw player 1
SelectObject (buffer,player2brush); //do the same for p2
Rectangle (buffer,p[1].x-PLSIZE,p[1].y-PLSIZE,p[1].x+PLSIZE,p[1].y+PLSIZE);
if (bcount[0] > 0) //if p1 has a bullet
{
SelectObject (buffer, blackBrush); //select black brush
for (int i = 0; i < bcount[0]; ++i) //draw bullet(s)
{
Ellipse (buffer, bullet [0][i].x - BSIZE, bullet [0][i].y - BSIZE, bullet [0][i].x + BSIZE, bullet [0][i].y + BSIZE);
}
}
if (bcount[1] > 0) //same goes for p2
{
SelectObject (buffer, blackBrush);
for (int i = 0; i < bcount[1]; ++i)
{
Ellipse (buffer, bullet [1][i].x - BSIZE, bullet [1][i].y - BSIZE, bullet [1][i].x + BSIZE, bullet [1][i].y + BSIZE);
}
}
BitBlt(hdc, 0,0, r.right , r.bottom, buffer, 0,0, SRCCOPY); //copy buffer bitmap to window
DeleteObject (player1brush); //delete stuff
DeleteObject (player2brush);
DeleteObject (pen);
SelectObject (buffer, oldBM);
DeleteObject (bitmap);
DeleteDC(buffer);
}
void moveBullets (HWND hwnd) //move the bullets ***PROBLEM AREA***
{
RECT r;
GetClientRect (hwnd, &r); //get canvas area
if (bcount[0] > 0) //if p1 has bullet(s)
{
for (int i = 0; i < bcount[0]; ++i) //go through p1's bullets
{
///DOESN'T WORK
bullet [0][i].x += sin (bullet [0][i].angle * M_PI / 180); //move the bullet horizontally
if (bullet [0][i].x - BSIZE < 1 || bullet [0][i].x + BSIZE > r.right) //if out of bounds
{
deleteBullet (0, bullet [0][i].index); //delete the bullet
--i; //if bullet [2] was deleted, bullet [2] will now be the old bullet [3] so recheck this one next time
}
///WORKS PERFECTLY
bullet [0][i].y -= cos (bullet [0][i].angle * M_PI / 180); //do same for y, including middle border
if (bullet [0][i].y - BSIZE < 1 || bullet [0][i].y + BSIZE > r.bottom / 2 - 5)
{
deleteBullet (0, bullet [0][i].index);
--i;
}
}
}
if (bcount[1] > 0) //exact same thing (I checked a LOT) for p2
{
for (int i = 0; i < bcount[1]; ++i)
{
///WORKS PERFECTLY (at least in the p1 sense, there is a slight problem)
bullet [1][i].x += sin (bullet [1][i].angle * M_PI / 180);
if (bullet [1][i].x - BSIZE < 1 || bullet [1][i].x + BSIZE > r.right)
{
deleteBullet (1, bullet [1][i].index);
--i;
}
///WORKS PERFECTLY
bullet [1][i].y -= cos (bullet [1][i].angle * M_PI / 180);
if (bullet [1][i].y - BSIZE < r.bottom / 2 + 5 || bullet [1][i].y + BSIZE > r.bottom)
{
deleteBullet (1, bullet [1][i].index);
--i;
}
}
}
}
void deleteBullet (int player, int index) //delete bullet ***PROBLEM AREA***
{
if (index != bcount [player] - 1) //if it isn't the last bullet
{
for (int j = index; j < bcount[player] - 1; ++j) //go from here to the end of the current bullets - 1
{
bullet [player][j] = bullet [player][j+1]; //copy the next bullet into this spot
--bullet [player][j].index; //change the index of the bullet since it was moved back one
}
}
--bcount [player]; //lessen the bullet count, this is all that's needed if it's the last bullet
}
Any help with said problems or with something else you notice would be greatly appreciated.
EDIT: one side thing I forgot to ask was if there was a better way to continuously do things like move bullets for example than to put all that outside of the switch in the window procedure and the DefWindowProc after it.
It looks like deleteBullet may get called twice on the same i inside the loop for (i=0...) in moveBullets. I think this could happen if the bullet is moving diagonally. I'm not sure it's the cause of the trouble, though.
First English question I've seen on stackoverflow!