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 6 : Bat action

Previous step | Next step

Ok, up until now our game has not been so exciting, just a scrolling background and some border graphics. Time to make it more interesting! We will now add a bat to the game, and also make the bat move automatically so we have a nice demo application!

Bat
Our bat, including a three frame "game over animation".

First, download the file bat.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_BAT.

Add the following code to myapplication.h:

//-----------------------------------------------------------------------------
// Name: class CBreakBat
// Desc: Everything related to the bat
//-----------------------------------------------------------------------------
class CBreakBat
{
// Attributes
protected:
    float m_nX;
    float m_nY;
    float m_nXSpeed;
    CGapiSurface* m_pBatSurface;
 
// Construction
public:
    CBreakBat(CGapiDraw* pGlobal, float nX=0.0, float nY=0.0)
    {
        m_nX = nX;
        m_nY = nY;
        m_nXSpeed = 0;
        m_pBatSurface = new CGapiSurface(pGlobal);
    }
 
// Operations
public:
    void ResetBat()
    {
        m_nX = 0;
        m_nY = 0;
        m_nXSpeed = 0;
    }
    int GetScreenX() { return (int) m_nX; };
    int GetScreenY() { return (int) m_nY; };
    float GetX() { return m_nX; }
    float GetY() { return m_nY; }
    float GetXSpeed() { return m_nXSpeed; }
    DWORD GetWidth() { return m_pBatSurface->GetWidth(); }
    DWORD GetHeight() { return (m_pBatSurface->GetHeight() / GAMEPARAM_NUMBATS); }
    CGapiSurface* GetSurface() { return m_pBatSurface; }
    void SetScreenX(int nX) { m_nX = (float) nX; }
    void SetScreenY(int nY) { m_nY = (float) nY; }
    void SetX(float nX) { m_nX = nX; }
    void SetY(float nY) { m_nY = nY; }
    void SetXSpeed(float nSpeed) { m_nXSpeed = nSpeed; }
    void MoveBatX() { m_nX += m_nXSpeed; }
 
    float BallBounce(CBreakBall* pBall)
    {
        float nDistance = GetX() + (float)(GetWidth() >> 1) - (float)(pBall->GetX() + (pBall->GetWidth() >> 1));
        nDistance = - nDistance / (float) (GetWidth() >> 1);
        return (float) nDistance * 8.0f;
    }
    HRESULT DrawBat(CGapiSurface* pSurface)
    {
        int nBatX = GetScreenX();
        int nBatY = GetScreenY();
        return pSurface->BltFast(nBatX, nBatY, GetSurface(), CRect(0, 0, GetWidth(), GetHeight()), GDBLTFAST_KEYSRC, NULL);
    }
    HRESULT DrawBatShadow(CGapiSurface* pSurface)
    {
        int nBatX = GetScreenX() + 2;
        int nBatY = GetScreenY() + 2;
        GDBLTFASTFX bltfx;
        bltfx.dwFillColor = RGB(0,0,0);
        bltfx.dwOpacity = 64;
        return pSurface->BltFast(nBatX, nBatY, GetSurface(), CRect(0, 0, GetWidth(), GetHeight()), GDBLTFAST_KEYSRC | GDBLTFAST_COLORFILL | GDBLTFAST_OPACITY, &bltfx);
    }
 
    HRESULT CreateSurface(HINSTANCE hInstance)
    {
        HRESULT hr;
        if (SUCCEEDED(hr = m_pBatSurface->CreateSurface(0, hInstance, IDB_BAT, _T("PNG"))))
        {
            m_pBatSurface->SetColorKey(RGB(255, 0, 255));
        }
        return hr;
    }
 
    // Implementation
public:
    virtual ~CBreakBat()
    {
        delete m_pBatSurface;
    };
};
 
class CMyApplication : public CGapiApplication
{
    ...
    CBreakBat* m_pBat;
    ...
}

Add the following code to myapplication.cpp:

CMyApplication::CMyApplication(const GDAPPCONFIG& config) : CGapiApplication(config)
{
    ...
    m_pBat = new CBreakBat(GetGlobal());
    ...
}
 
CMyApplication::~CMyApplication()
{
    ...
    delete m_pBat;
    ...
}
 
HRESULT CMyApplication::CreateVidMemSurfaces(CGapiSurface* pBackBuffer, HINSTANCE hInstance)
{
    ...
    m_pBat->CreateSurface(hInstance);
    ...
}
 
HRESULT CMyApplication::OnDisplayChanged(CGapiSurface* pBackBuffer)
{
    ...
    int nBatWidth = m_pBat->GetWidth();
    m_pBat->ResetBat();
    m_pBat->SetScreenX(nScreenXCenter - (nBatWidth>>1));
    m_pBat->SetScreenY(nScreenHeight - 2*GAMEPARAM_BORDERWIDTH - GAMEPARAM_SCOREHEIGHT);
    ...
}
 
HRESULT CMyApplication::GameInit(CGapiSurface* pBackBuffer, DWORD dwFlags)
{
    ...
    int nBatWidth = m_pBat->GetWidth();
    m_pBat->ResetBat();
    m_pBat->SetScreenX(nScreenXCenter - (nBatWidth>>1));
    m_pBat->SetScreenY(nScreenHeight - 2*GAMEPARAM_BORDERWIDTH - GAMEPARAM_SCOREHEIGHT);
    ...
}
 
HRESULT CMyApplication::GameMoveBall(CGapiSurface* pBackBuffer)
{
    int nScreenWidth = pBackBuffer->GetWidth();
    int nScreenHeight = pBackBuffer->GetHeight();
    int nScreenXCenter = nScreenWidth >> 1;
    int nScreenYCenter = nScreenHeight >> 1;
 
    m_pBall->MoveBall();
 
    // Bounce ball on top of display
    if (m_pBall->GetScreenY() < GAMEPARAM_BORDERWIDTH)
    {
        m_pBall->SetYSpeed(-m_pBall->GetYSpeed());
        m_pBall->SetScreenY(GAMEPARAM_BORDERWIDTH);
    }
 
    // Loose life if ball misses the bat
    if (m_pBall->GetScreenY() >= (int) (nScreenHeight-m_pBall->GetHeight() - GAMEPARAM_SCOREHEIGHT))
    {
        if (m_dwGameMode == GAMEMODE_INGAME)
        {
            m_dwGameBallsLeft--;
            if (m_dwGameBallsLeft == 0)
            {
                m_dwGameMode = GAMEMODE_INTRO;
            }
        }
        // Reset ball position
        m_pBall->SetScreenX(m_pBat->GetScreenX() + (m_pBat->GetWidth() >> 1) - (m_pBall->GetWidth() >> 1) - 8);
        m_pBall->SetScreenY(GAMEPARAM_BRICKOFFSETY + GAMEPARAM_NUMLINES * (GAMEPARAM_BRICKHEIGHT + GAMEPARAM_BRICKSPACE));
        m_pBall->SetYSpeed(m_nGameBallSpeed);
        m_pBall->SetXSpeed(0);
    }
    if (m_pBall->GetScreenX() >= (int) (nScreenWidth - GAMEPARAM_BORDERWIDTH - m_pBall->GetWidth()))
    {
        m_pBall->InvertXSpeed();
        m_pBall->SetScreenX(nScreenWidth - GAMEPARAM_BORDERWIDTH - m_pBall -> GetWidth());
    }
    if (m_pBall->GetScreenX() < GAMEPARAM_BORDERWIDTH)
    {
        m_pBall->InvertXSpeed();
        m_pBall->SetScreenX(GAMEPARAM_BORDERWIDTH);
    }
    return S_OK;
}
 
HRESULT CMyApplication::GameCheckBat(CGapiSurface* pBackBuffer)
{
    int nScreenWidth = pBackBuffer->GetWidth();
    int nScreenHeight = pBackBuffer->GetHeight();
    int nScreenXCenter = nScreenWidth >> 1;
    int nScreenYCenter = nScreenHeight >> 1;
    int nBatX = m_pBat->GetScreenX();
    int nBatWidth = m_pBat->GetWidth();
    int nBatHeight = m_pBat->GetHeight();
    int nBallX = m_pBall->GetScreenX();
    int nBallY = m_pBall->GetScreenY();
    int nBallWidth = m_pBall->GetWidth();
    int nBallHeight = m_pBall->GetHeight();
 
    // Check for bat to border collision
    if (nBatX < GAMEPARAM_BORDERWIDTH)
    {
        m_pBat->SetScreenX(GAMEPARAM_BORDERWIDTH);
    }
    else if (nBatX >= (nScreenWidth - GAMEPARAM_BORDERWIDTH - nBatWidth))
    {
        m_pBat->SetScreenX(nScreenWidth - GAMEPARAM_BORDERWIDTH - nBatWidth);
    }
 
    // Check for bat to ball collision
    if (nBallY >= (nScreenHeight - GAMEPARAM_SCOREHEIGHT - 2*GAMEPARAM_BORDERWIDTH - nBallWidth))
    {
        if (nBallY < (nScreenHeight - GAMEPARAM_SCOREHEIGHT - 2*GAMEPARAM_BORDERWIDTH))
        {
            if (((nBallX + nBallWidth) >= nBatX) && (nBallX <= (nBatX + nBatWidth)))
            {
                m_pBall->SetScreenY(nScreenHeight - GAMEPARAM_SCOREHEIGHT - nBallHeight - 2*GAMEPARAM_BORDERWIDTH);
                m_pBall->InvertYSpeed();
                m_pBall->SetXSpeed(m_pBat->BallBounce(m_pBall));
            }
        }
    }
    return S_OK;
}
 
HRESULT CMyApplication::GameMoveBat(CGapiSurface* pBackBuffer)
{
    int nBallX = m_pBall->GetScreenX();
    int nBatX = m_pBat->GetScreenX();
    int nBatWidth = m_pBat->GetWidth();
 
    if (m_dwGameMode == GAMEMODE_INGAME)
    {
        m_pBat->MoveBatX();
        return S_OK;
    }
 
    // Make it cheat and move a bit faster on the intro screen so it never fails
    if (nBallX < (nBatX))
    {
        m_pBat->SetXSpeed((float)(nBallX - nBatX));
        m_pBat->MoveBatX();
    }
    else if (nBallX > (nBatX + nBatWidth))
    {
        m_pBat->SetXSpeed(nBallX - nBatWidth - nBatX + 1.0f);
        m_pBat->MoveBatX();
    }
 
/*
    // Uncomment this code and comment the above section if you
    // want the bat to move automatically according to the player rules
    if (nBallX < (nBatX + m_nGameBallSpeed))
    {
        m_pBat->SetXSpeed(-m_nGameBallSpeed);
        m_pBat->MoveBatX();
    }
    else if (nBallX > (nBatX + nBatWidth - m_nGameBallSpeed))
    {
        m_pBat->SetXSpeed(m_nGameBallSpeed);
        m_pBat->MoveBatX();
    }
*/
 
    return S_OK;
}
 
HRESULT CMyApplication::GameDrawBackgrund(CGapiSurface* pBackBuffer)
{
    ...
    // Draw ball shadow and bat shadow behind other objects
    m_pBall->DrawBallShadow(pBackBuffer);
    m_pBat->DrawBatShadow(pBackBuffer);
    ...
}
 
HRESULT CMyApplication::GameDrawObjects(CGapiSurface* pBackBuffer)
{
    int nScreenWidth = pBackBuffer->GetWidth();
    int nScreenHeight = pBackBuffer->GetHeight();
 
    // Draw ball and bat
    m_pBall->DrawBall(pBackBuffer);
    m_pBat->DrawBat(pBackBuffer);
 
    return S_OK;
}

You should now have a moving ball and moving bat on your screen, and the ball should properly reflect on the bat and the walls!

Now let's add some bricks!