Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Grausteroids - an Asteroids game using DirectX and C++

0.00/5 (No votes)
7 Apr 2002 1  
An Asteroid's clone which needs some work but will illustrate some points about writing games.

Sample Image - Grausteroids.jpg

Overview

I've noticed more and more people asking questions about writing games, so I thought I'd post this. It was written some time ago, as an excuse to learn various parts of DirectX. This game will detect if you have a joystick, and use it. If the joystick has force-feedback, it is supported. It is not complete, it is playable, but the main splash screen opens an area that should have included various options such as key selection and was never written. The release version also sometimes crashes on exit and leaks memory during the shutdown of the sound engine ( i.e., for half a second ). It's not perfect, but I don't have time to fix it, and on the other hand, I don't see any reason to let it rot on my hard drive when it might be enough to help someone. I believe the core code to manage the window in MFC came from CP, the code to play MP3s certainly did, although I did not use it. This code compiled under DirectX version 8, which was a pleasant surprise, so it can be taken on by anyone who wants to experiment with it.

The Sound Engine

The sound engine was designed to simplify the process of using Direct Sound by allowing sounds to be added to a player and called from there. I wanted to also use MP3s, but the only code I could find was in C, so only one mp3 is supported. This is a shame, because the sounds and images are the bulk of the download. The player also reports memory leaks during shutdown. Anyone who wants to take this on and finish it is welcome.

Game Speed

One area that I was able to get to where I wanted it was game speed. The game draws at the fastest frame rate it can manage and keeps it's speed constant by checking on each draw if a certain amount of time has elapsed in order to call functions to move the ship and the asteroids. They are separate so that, as the game gets harder, the asteroids move faster but the ship does not.

if (timeGetTime() - m_LastTime > (unsigned int) (30-((m_iLevel>15) ? 
                                                 30 : m_iLevel * 2)))
{
    m_LastTime = timeGetTime();
    MoveStuff();
}

if (timeGetTime() - m_ShipTimer > 30)
{
    m_ShipTimer = timeGetTime();
    MoveShip();
}

The game defines two classes for data storage:

class floatpoint
{
public:
    float x;
    float y;
};

class gameitem 
{
public:
    int          Type;
    floatpoint   ptPos;
    float        iMoveX;
    float        iMoveY;
    int          iSprite;
    int          iTime;
    CRect        rcPosition;
};

floatpoint is used to track the ship's acceleration and position, because I use trig to figure out an acceleration based on the angle I am pointing. The gameitem class is used to track asteroids. Those who know me well will be amused to note that I used to use CArray rather than vector, and that this game uses MFC.

There is a lot of other stuff going on in this game, but I don't remember most of it. If you find this article interesting but you don't understand how something works, just email me, and as I recollect details through emails I get, I will update the article. Initially my desire is simply that someone get use out of this, because I am not doing anything with it.

Pixel perfect collisions

Another area I did a lot of work was in making collisions pixel perfect. The methodology is the same as used in my C# game, Collision, I suggest you read that article for a thorough explanation. The code under C++/DirectX looks like this:

while (rcShip.top > 0 && rcAsteroid.top > 0)
{
    rcShip.OffsetRect(CPoint(0,-1));
    rcAsteroid.OffsetRect(CPoint(0,-1));
}

while (rcShip.left > 0 && rcAsteroid.left > 0)
{
    rcShip.OffsetRect(CPoint(-1,0));
    rcAsteroid.OffsetRect(CPoint(-1,0));
}

CRect rcTest;
if (!rcTest.IntersectRect(rcShip, rcAsteroid))
    TRACE ("Intersect failed\r\n");

RECT rc;

rc.top = ((m_iShipSprite/10) * 32)+576;
rc.left = ((m_iShipSprite%10) * 32);
rc.right = rc.left + 32;
rc.bottom = rc.top + 32;

if (m_bShield)
{
    rc.bottom += 128;
    rc.top += 128;
}

DDBLTFX dbltfx;
dbltfx.dwSize = sizeof(DDBLTFX);
dbltfx.dwFillColor = RGB(0,0,0);
m_pIShip->Blt(NULL,NULL,NULL,DDBLT_COLORFILL,&dbltfx);
m_pIShip->BltFast(rcShip.left,rcShip.top,m_pISprites,
                  &rc, DDBLTFAST_WAIT);

switch (m_Items[i].Type)
{
    case big:
        rc.top = ((m_Items[i].iSprite/5) * 64);
        rc.left = ((m_Items[i].iSprite%5) * 64);
        rc.right = rc.left + 62;
        rc.bottom = rc.top + 64;
        break;
    case mid:
        rc.top = ((m_Items[i].iSprite/10) * 32) + 384;
        rc.left = ((m_Items[i].iSprite%10) * 32);
        rc.right = rc.left + 32;
        rc.bottom = rc.top + 32;
        break;
    case sml:
        rc.top = ((m_Items[i].iSprite/20) * 16) + 512;
        rc.left = ((m_Items[i].iSprite%20) * 16);
        rc.right = rc.left + 16;
        rc.bottom = rc.top + 16;
        break;    
}

m_pIAsteroid->Blt(NULL,NULL,NULL,DDBLT_COLORFILL,&dbltfx);
m_pIAsteroid->BltFast(rcAsteroid.left,rcAsteroid.top,
                      m_pISprites,&rc,DDBLTFAST_WAIT);

HDC hdcShip;
m_pIShip->GetDC(&hdcShip);
CDC dcShip;
dcShip.Attach(hdcShip);

HDC hdcAsteroid;
m_pIAsteroid->GetDC(&hdcAsteroid);
CDC dcAsteroid;
dcAsteroid.Attach(hdcAsteroid);

for ( int x = rcTest.left; x<=rcTest.right; x++)
    for ( int y = rcTest.top;y<=rcTest.bottom; y++)
        if (dcShip.GetPixel(x,y) != RGB(0,0,0) &&
            dcAsteroid.GetPixel(x,y) != RGB(0,0,0))
        {
            dcAsteroid.Detach();
            m_pIAsteroid->ReleaseDC(hdcAsteroid);
            dcShip.Detach();
            m_pIShip->ReleaseDC(hdcShip);
            return true;
        }
        
dcAsteroid.Detach();
m_pIAsteroid->ReleaseDC(hdcAsteroid);
dcShip.Detach();
m_pIShip->ReleaseDC(hdcShip);
return false;

The main point of interest is the last nested loop, where we use GetPixel to establish if any pixels in the intersected area indicate a collision. Nowadays I would not use GetPixel, but for all that, it works surprisingly well. I would instead use a DIBSection and access the bits directly.

Playing the game

Basically to play the game, just use the arrow keys, left and right to rotate, up to accelerate and down to use your limited ( but recharging ) shield. Space fires.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here