两名游戏玩家之间的行为不一致

时间:2011-09-22 21:47:25

标签: c++ winapi window gdi

我一直在制作一个英语演示游戏(我知道对吗?)我最近遇到了一些奇怪的问题。

在这个阶段,有两个可以发射子弹的玩家。当子弹击中屏幕的一侧或中间边界时,它们应该消失,并可能在以后重复使用。

问题是,首先,当任一玩家向上或向下射击时,子弹就像他们应该的那样消失在屏幕的顶部/底部/中间。如果玩家2向侧面射击,一旦击中,他们似乎全部消失(只有玩家2的子弹)并且我必须等待几秒才能再次开始射击。

主要的问题是,即使我使用为播放器1而不是播放器2修改的完全相同的代码,当玩家1的第一次拍摄到屏幕的一侧到达时,它会对程序进行分段。

真正奇怪的是,有一次,这发生了,并且没有改变任何东西,我再次运行它,这一切都很好。这可能与玩家1和2之间的子弹顺序有关,甚至可能与我先射击的位置有关。我试过重新创建它,但还没有结果。

在您尝试编译代码之前只需注意,我使用了一个包装器来轻松制作窗口。我注意到///注释幕后的内容,所以将这些信息添加到用于制作窗口的任何方法中都可以正常工作。

问题区域列在底部附近:

///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
}

对所述问题的任何帮助或您注意到的其他事情将不胜感激。

编辑:我忘了问的一件事是,是否有更好的方法来继续执行诸如移动项目符号之类的操作,而不是将所有内容放在窗口过程中的开关之外,以及之后的DefWindowProc

1 个答案:

答案 0 :(得分:0)

看起来deleteBullet可能会在moveBullets中的循环i内的同一for (i=0...)上被调用两次。我认为如果子弹沿对角方向移动可能会发生这种情况。不过,我不确定这是麻烦的原因。

我在stackoverflow上看到的第一个英文问题!