Documentation
[SDK Documentation] [Creating a complete game: Breakout]

 

Creating a complete game: Breakout

This document has been updated for use with GapiDraw 4.0 or later.
Last updated on October 6, 2008.

 

Step 7 : Bricks

Previous step | Next step

We will now add bricks to the game. We will do this using a new structure called BREAKBRICK, and we will also calculate the number of bricks on the game board dynamically - so that the entire display is filled with bricks.

Bricks
Our Bricks. We will only use the first three in our game.

First, download the file bricks.png and save it to your Breakout\Common\res folder. Then add it to your Visual Studio project as a PNG image and name it IDB_BRICKS.

Add the following code to myapplication.h:

//-----------------------------------------------------------------------------
// Name: struct CBreakBrick
// Desc: Everything related to bricks
//-----------------------------------------------------------------------------
typedef struct _BREAKBRICK
{
    BOOL bShowBrick;
    DWORD dwBrickIndex;
    DWORD dwShineIndex;
    DWORD dwExplosionIndex;
    int nExplosionCenterX;
    int nExplosionCenterY;
} BREAKBRICK;
 
class CMyApplication : public CGapiApplication
{
    ...
    BREAKBRICK* m_pBricks;
    CGapiSurface* m_pBrickSurface;
    DWORD m_dwGameBricksPerLine;
    ...
}

Add the following code to myapplication.cpp:

CMyApplication::CMyApplication(const GDAPPCONFIG& config) : CGapiApplication(config)
{
    ...
    m_pBricks = NULL;
    m_pBrickSurface = new CGapiSurface(GetGlobal());
    m_dwGameBricksPerLine = 0;
    ...
}
 
CMyApplication::~CMyApplication()
{
    ...
    if (m_pBricks) delete m_pBricks;
    delete m_pBrickSurface;
    ...
}
 
HRESULT CMyApplication::CreateVidMemSurfaces(CGapiSurface* pBackBuffer, HINSTANCE hInstance)
{
    ...
    m_pBrickSurface->CreateSurface(0, hInstance, IDB_BRICKS, _T("PNG"));
    ...
}
 
HRESULT CMyApplication::GameCheckBricks(CGapiSurface* pBackBuffer)
{
    int nScreenWidth = pBackBuffer->GetWidth();
    int nScreenHeight = pBackBuffer->GetHeight();
    int nScreenXCenter = nScreenWidth >> 1;
    int nScreenYCenter = nScreenHeight >> 1;
    int nBallX = m_pBall->GetScreenX();
    int nBallY = m_pBall->GetScreenY();
    int nBallWidth = m_pBall->GetWidth();
    int nBallHeight = m_pBall->GetHeight();
    
    // Check to see if the ball is outside brick area
    if (nBallY < (GAMEPARAM_BRICKOFFSETY - nBallHeight) || nBallY > (GAMEPARAM_BRICKOFFSETY + GAMEPARAM_NUMLINES * (GAMEPARAM_BRICKSPACE + GAMEPARAM_BRICKHEIGHT)))
    {
        return S_OK;
    }
    
    // Check for brick collision
    int nBrickOffsetX = (nScreenWidth - 2 * GAMEPARAM_BORDERWIDTH - m_dwGameBricksPerLine * (GAMEPARAM_BRICKWIDTH + GAMEPARAM_BRICKSPACE)) >> 1;
    for (int nRowY = 0; nRowY < GAMEPARAM_NUMLINES; nRowY++)
    {
        for (int nRowX = 0; nRowX < (int)m_dwGameBricksPerLine; nRowX++)
        {
            if (m_pBricks[nRowY * (int) m_dwGameBricksPerLine + nRowX].bShowBrick)
            {
                int nY = GAMEPARAM_BRICKOFFSETY + nRowY * (GAMEPARAM_BRICKSPACE + GAMEPARAM_BRICKHEIGHT);
                int nX = GAMEPARAM_BORDERWIDTH + nBrickOffsetX + nRowX * (GAMEPARAM_BRICKSPACE + GAMEPARAM_BRICKWIDTH);
                if (nBallY >= (nY - nBallHeight) && nBallY < (nY + GAMEPARAM_BRICKHEIGHT) &&
                    nBallX >= (nX - nBallWidth) && nBallX < (nX + GAMEPARAM_BRICKWIDTH))
                {
                    // Remove brick and trigger an explosion
                    int nBrickIndex = nRowY * m_dwGameBricksPerLine + nRowX;
                    m_pBricks[nBrickIndex].bShowBrick = FALSE;
                    m_pBricks[nBrickIndex].dwShineIndex = 0;
                    m_pBricks[nBrickIndex].dwExplosionIndex = GAMEPARAM_NUMEXPLOSIONS;
                    m_pBricks[nBrickIndex].nExplosionCenterX = nBallX + (nBallWidth >> 1);
                    m_pBricks[nBrickIndex].nExplosionCenterY = nBallY + (nBallHeight >> 1);
    
                    // Calculate Bounce left and right - remember that ball moves in subpixel increments
                    if ((nBallX <= (nX - nBallWidth + 1)) || (nBallX >= (nX + GAMEPARAM_BRICKWIDTH - 1)))
                    {
                        m_pBall->InvertXSpeed();
                    }
    
                    // Calculate Bounce top and bottom - remember that ball moves in subpixel increments
                    if ((nBallY <= (nY - nBallHeight + 1)) || (nBallY >= (nY + GAMEPARAM_BRICKHEIGHT - 1)))
                    {
                        m_pBall->InvertYSpeed();
                    }
     
                    // Finally award points for the brick
                    if (m_dwGameMode == GAMEMODE_INGAME)
                    {
                        m_dwPlayerScore += (GAMEPARAM_NUMLINES - nRowY);
                    }
    
                    // Only break one brick at the time
                    return S_OK;
                }
            }
        }
    }
    return S_OK;
}
 
HRESULT CMyApplication::GameDrawObjects(CGapiSurface* pBackBuffer)
{
    // Bricks
    BOOL bLevelClear = TRUE;
    DWORD dwColorShift = 255/(GAMEPARAM_NUMLINES);
    DWORD dwBrickOffsetX = (nScreenWidth - 2 * GAMEPARAM_BORDERWIDTH - m_dwGameBricksPerLine * (GAMEPARAM_BRICKWIDTH + GAMEPARAM_BRICKSPACE)) >> 1;
    GDFILLRECTFX fillfx;
    fillfx.dwOpacity = 64;
    DWORD dwX, dwY;
    for (dwY=0; dwY < GAMEPARAM_NUMLINES; dwY++)
    {
        for (dwX=0; dwX < m_dwGameBricksPerLine; dwX++)
        {
            DWORD dwBrickIndex = dwY * m_dwGameBricksPerLine + dwX;
            if (m_pBricks[dwBrickIndex].bShowBrick)
            {
                bLevelClear = FALSE;
                DWORD dwLeft = GAMEPARAM_BORDERWIDTH + dwBrickOffsetX + dwX * (GAMEPARAM_BRICKWIDTH + GAMEPARAM_BRICKSPACE);
                DWORD dwTop = GAMEPARAM_BRICKOFFSETY  + dwY * (GAMEPARAM_BRICKHEIGHT + GAMEPARAM_BRICKSPACE);
                DWORD dwRight = dwLeft + GAMEPARAM_BRICKWIDTH;
                DWORD dwBottom = dwTop + GAMEPARAM_BRICKHEIGHT;
                DWORD dwXOffset = m_pBricks[dwBrickIndex].dwBrickIndex * GAMEPARAM_BRICKWIDTH;
                pBackBuffer->FillRect(CRect(dwLeft+2, dwTop+2, dwRight+2, dwBottom+2), RGB(0, 0, 0), GDFILLRECT_OPACITY, &fillfx);
                pBackBuffer->BltFast(dwLeft, dwTop, m_pBrickSurface, CRect(dwXOffset, 0, dwXOffset+GAMEPARAM_BRICKWIDTH, GAMEPARAM_BRICKHEIGHT), 0, NULL);
            }
        }
    }
    if (bLevelClear)
    {
        GameInitBricks(pBackBuffer);
        if (m_dwGameMode == GAMEMODE_INGAME)
        {
            m_dwPlayerScore += 100;
        }
    }
 
    return S_OK;
}
 
HRESULT CMyApplication::GameInitBricks(CGapiSurface* pBackBuffer)
{
    for (DWORD dwBrickIndex=0; dwBrickIndex < GAMEPARAM_NUMLINES * m_dwGameBricksPerLine; dwBrickIndex++)
    {
        m_pBricks[dwBrickIndex].bShowBrick = TRUE;
        m_pBricks[dwBrickIndex].dwBrickIndex = (DWORD) (3.0f * (float) rand() / (float) RAND_MAX);
        m_pBricks[dwBrickIndex].dwShineIndex = 0;
        m_pBricks[dwBrickIndex].dwExplosionIndex = 0;
        m_pBricks[dwBrickIndex].nExplosionCenterX = -1;
        m_pBricks[dwBrickIndex].nExplosionCenterY = -1;
    }
    return S_OK;
}
 
HRESULT CMyApplication::OnDisplayChanged(CGapiSurface* pBackBuffer)
{
    ...
    DWORD dwGameBricksPerLine = (nScreenWidth - 2 * GAMEPARAM_BORDERWIDTH) / (GAMEPARAM_BRICKWIDTH + GAMEPARAM_BRICKSPACE);
    if (dwGameBricksPerLine < m_dwGameBricksPerLine)
    {
        // Screen width has been decreased, we need to redraw the bricks
        m_dwGameBricksPerLine = dwGameBricksPerLine;
        if (m_pBricks)
        {
            delete m_pBricks;
        }
        m_pBricks = new BREAKBRICK[m_dwGameBricksPerLine * GAMEPARAM_NUMLINES];
        GameInitBricks(pBackBuffer);
        if (m_dwGameMode == GAMEMODE_INGAME)
        {
            // Reset the player score and ball index as well if we are in-game
            m_dwPlayerScore = 0;
            m_dwGameBallsLeft = 3;
        }
    }
    ...
}
 
HRESULT CMyApplication::GameInit(CGapiSurface* pBackBuffer, DWORD dwFlags)
{
    ...
    if (m_pBricks)
    {
        delete m_pBricks;
    }
    m_dwGameBricksPerLine = (nScreenWidth - 2 * GAMEPARAM_BORDERWIDTH) / (GAMEPARAM_BRICKWIDTH + GAMEPARAM_BRICKSPACE);
    m_pBricks = new BREAKBRICK[m_dwGameBricksPerLine * GAMEPARAM_NUMLINES];
    GameInitBricks(pBackBuffer);
    ...
}

Phew, that's quite some code just to add some bricks!

Well, now on to more interesting things - explosions!