当屏幕上有多个精灵时,win32位图会闪烁

时间:2011-09-13 00:38:52

标签: winapi bitmap transparency sprite bitblt

我一直在用win32 api制作精灵游戏。出于某种原因,当我在屏幕上有多个精灵时,它们偶尔会闪烁,好像它们正在消失并返回。当屏幕上只有一个精灵时,它会正确显示。

我正在使用C ++,win32 API并使用Visual Studio 08

以下是我所拥有的:

//creates rect based on window client area
GetClientRect(ghwnd, &screenRect);  
// Initialises front buffer device context (window)
frontHDC = GetDC(ghwnd);    
// sets up Back DC to be compatible with the front  
backHDC = CreateCompatibleDC(frontHDC);
// Create another hdc to store the bitmap in before the backbuffer
bitmapHDC = CreateCompatibleDC(frontHDC);
//creates bitmap compatible with the front buffer
theOldFrontBitMap = CreateCompatibleBitmap(frontHDC, screenRect.right, screenRect.bottom);
//creates bitmap compatible with the back buffer
theOldBackBitMap = (HBITMAP)SelectObject(backHDC, theOldFrontBitMap);

HBITMAP originalBitMap = (HBITMAP)SelectObject(bitmapHDC,bitmap);

//Transparency function
TransparentBlt( backHDC,
                m_Position.x,
                m_Position.y,
                m_Size.x,
                m_Size.y,
                bitmapHDC,
                0,
                0,
                m_Size.x,
                m_Size.y,
                0x00FFFFFF);

SelectObject(bitmapHDC,originalBitMap);

BitBlt(frontHDC, screenRect.left, screenRect.top, 
       screenRect.right, screenRect.bottom, backHDC, 0, 0, SRCCOPY);

我这样做是否正确?如果是的话,我哪里错了?如果我没有提供足够的信息,请告诉我,我会纠正这一点。

2 个答案:

答案 0 :(得分:2)

创建Win32游戏的问题在于,即使您使用双缓冲,也无法等待显示器的垂直回扫显示缓冲区。

在垂直回扫过程中显示缓冲区或精灵可能会导致撕裂甚至消失的精灵。

唯一真正的方法是使用像OpenGL或DirectX这样的SDK来管理和显示缓冲区。

这是一个可以帮助您的示例程序,使用箭头键移动双缓冲背景上的白框:

#include <Windows.h>

RECT rcSize;
HDC hdcBackBuffer, hdcSprite;
HBITMAP hbmBackBuffer, hbmSprite;
int spriteX = 175, spriteY = 175;

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    static PAINTSTRUCT ps;

    switch (msg)
    {
    case WM_CREATE:
        {
            HDC hdcWindow = GetDC(hWnd);

            // make back buffer
            GetClientRect(hWnd, &rcSize);
            hdcBackBuffer = CreateCompatibleDC(hdcWindow);
            hbmBackBuffer = CreateCompatibleBitmap(hdcBackBuffer, rcSize.right - rcSize.left, rcSize.bottom - rcSize.top);
            SelectObject(hdcBackBuffer, hbmBackBuffer);  // SHOULD SAVE PREVIOUS...

            // make sprite
            hdcSprite = CreateCompatibleDC(hdcWindow);
            hbmSprite = CreateCompatibleBitmap(hdcSprite, 50, 50);
            SelectObject(hdcSprite, hbmSprite);  // SHOULD SAVE PREVIOUS...
            RECT rcSprite;
            SetRect(&rcSprite, 0, 0, 50, 50);
            FillRect(hdcSprite, &rcSprite, (HBRUSH)GetStockObject(WHITE_BRUSH));

            ReleaseDC(hWnd, hdcWindow);
            return 0;
        }
    case WM_KEYDOWN:
        {
            // SHOULD REALLY USE GetAsyncKeyState for game, but simplified here
            switch (wParam)
            {
            case VK_LEFT:
                spriteX--;
                break;
            case VK_RIGHT:
                spriteX++;
                break;
            case VK_UP:
                spriteY--;
                break;
            case VK_DOWN:
                spriteY++;
                break;
            }
            return 0;
        }
    case WM_ERASEBKGND:
        {
            return 1; // INDICATE THAT WE ERASED THE BACKGROUND OURSELVES
        }
    case WM_PAINT:
        {
            BeginPaint(hWnd, &ps);
            // clear back buffer
            FillRect(hdcBackBuffer, &rcSize, (HBRUSH)GetStockObject(BLACK_BRUSH));
            // render sprite to back buffer
            BitBlt(hdcBackBuffer, spriteX, spriteY, 50, 50, hdcSprite, 0, 0, SRCCOPY);
            // render back buffer to screen
            BitBlt(ps.hdc, 0, 0, rcSize.right - rcSize.left, rcSize.bottom - rcSize.top, hdcBackBuffer, 0, 0, SRCCOPY);
            EndPaint(hWnd, &ps);
            return 0;
        }
    case WM_DESTROY:
        {
            // TODO - DESTROY ALL BITMAPS AND DEVICE CONTEXTS
            PostQuitMessage(0);
            return 0;
        }
    default:
        {
            return DefWindowProc(hWnd, msg, wParam, lParam);
        }
    }
}

int WINAPI WinMain(HINSTANCE hPrevInstance, HINSTANCE hInstance, LPSTR lpCmdLine, int nShowCmd)
{
    static TCHAR className[] = TEXT("GameClass");
    static TCHAR windowName[] = TEXT("A Game");

    WNDCLASSEX wcex;

    wcex.cbClsExtra = 0;
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.cbWndExtra = 0;
    wcex.hbrBackground = NULL;
    wcex.hCursor = LoadCursor(hInstance, IDC_ARROW);
    wcex.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
    wcex.hIconSm = NULL;
    wcex.hInstance = hInstance;
    wcex.lpfnWndProc = WndProc;
    wcex.lpszClassName = className;
    wcex.lpszMenuName = NULL;
    wcex.style = 0;

    if (!RegisterClassEx(&wcex))
        return 0;

    HWND hWnd = CreateWindow(className, windowName, WS_CAPTION | WS_BORDER | WS_SYSMENU, 0, 0, 400, 400, NULL, NULL, hInstance, NULL);
    if (!hWnd)
        return 0;

    ShowWindow(hWnd, nShowCmd);
    UpdateWindow(hWnd);

    MSG msg;
    for (;;)
    {
        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
            {
                break;
            }
            else
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }

        InvalidateRect(hWnd, NULL, FALSE);
    }

    return msg.wParam;
}

答案 1 :(得分:1)

我认为你的后台缓冲区实现是错误的,虽然我不确定究竟在哪里。尝试单独的后台缓冲类的这种实现。我希望它有所帮助。

这是我的后台缓冲类。

BackBuffer::BackBuffer(HWND hWnd, int width, int height)
{
    //Save a copy of the main window handle
    mhWnd = hWnd;

    //Get a handle to the device context associated with
    // the window
    HDC hWndDC = GetDC(hWnd);

    //Save the backbuffer dimensions
    mWidth = width;
    mHeight = height;

    //Create system memory device context that is compatible
    //with the window one
    mhDC = CreateCompatibleDC(hWndDC);

    //Create the backbuffer surface bitmap that is compatible
    //with the window device context bitmap format. That is
    //the surface we will render onto.
    mhSurface = CreateCompatibleBitmap(hWndDC, width, height);

    //Done with DC
    ReleaseDC(hWnd, hWndDC);

    //At this point, the back buffer surface is uninitialized,
    //so lets clear it to some non-zero value. Note that it 
    //needs to be a non-zero. If it is zero then it will mess 
    //up our sprite blending logic.

    //Select the backbuffer bitmap into the DC
    mhOldObject = (HBITMAP)SelectObject(mhDC, mhSurface);

    //Select a white brush
    HBRUSH white = (HBRUSH)GetStockObject(WHITE_BRUSH);
    HBRUSH oldBrush = (HBRUSH)SelectObject(mhDC, white);

    //Clear the backbuffer rectangle
    Rectangle(mhDC, 0, 0, mWidth, mHeight);

    //Restore the original brush
    SelectObject(mhDC, oldBrush);

}

BackBuffer::~BackBuffer()
{
    SelectObject(mhDC, mhOldObject);
    DeleteObject(mhSurface);
    DeleteDC(mhDC);
}

HDC BackBuffer::getDC()
{
    return mhDC;
}

int BackBuffer::width()
{
    return mWidth;
}

int BackBuffer::height()
{
    return mHeight;
}

void BackBuffer::present()
{
    //Get a handle to the device context associated with 
    //the window
    HDC hWndDC = GetDC(mhWnd);

    //Copy the backbuffer contents over to the
    //window client area
    BitBlt(hWndDC, 0, 0, mWidth, mHeight, mhDC, 0, 0, SRCCOPY);

    //Free window DC when done
    ReleaseDC(mhWnd, hWndDC);
}

继承实施:

AudioDevice

尝试通过此实现工作,评论应该有助于您理解。希望这可以帮助。