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.