Click here to Skip to main content
Click here to Skip to main content

Collision - A C# Game, part 3: pixel perfect collision detection

By , 16 Apr 2002
Rate this:
Please Sign up or sign in to vote.

Sample Image - Collision3.jpg

Goals

Having come down a fair way from my original lofty plans, I remain determined that collision detection in this game should be perfect, not approximated.  This can only be done by direct access to the bitmap data.  As I've found everything so far to be slower than I would like, my first step was to look to optimise what I had.

JPEG vs. Bitmap

You'll recall my original resources were all jpegs to save space, but that I noted this meant I had to specify a colour range when drawing in order to mask the background.  For the sake of speed I have gone to bitmap resources, which meant drawing the backgrounds to all be hard black by hand.  This means you may find the game does not appear pixel perfect, because some of the old blue background might still be there, but not visible in the game.  I assure you the technique I am showing you is not at fault, my job in editing the bitmaps is.  The code is hopefully better prepared for the cost of our collision detection, but you'll notice the serious jump in the size of the exe and the project.  No new bitmaps were added, that's just the cost of bitmaps as opposed to jpegs.

MakeTransparent

Having done this, I can use the MakeTransparent method of Bitmap, passing in Color.Black as the parameter, and I do not have to worry again about masking - the image will now automatically draw with the black areas masked. 

Per Pixel Collisions

Which leaves me with the collision code.  The first step is easy, that is, to check if we've collided by checking the planet Rectangle against that of the ship.  This takes place in the same loop as the moving of planets and score keeping, but I've removed that code for clarity ( you saw it last time ).

 for (int i = 0; i < m_arAsteroids.Count; ++i)
{
     if (arTemp.rcAsteroid.IntersectsWith(m_rcShipPos))
    {
        // We've hit
    }                        

But the problem is that as you recall, we have one huge planet, so the others all have a fair amount of transparency in them.  Even if this was not so, no game player would be happy to find they die if they come within the rectangle that defines the planet they are avoiding.  We need to do better.  The solution is to figure out the rectangle that defines the area shared by both objects, normalise that rectangle for both bitmaps and then step through them together to see if any pixel position in the intersected rectangle contains a pixel vale that is not masked in both bitmaps.  The main trick is to limit the area we step through as much as possible, because it's not a cheap operation.

 private bool HitTest(Asteroid a) 
{ 
    Rectangle rcIntersect = a.rcAsteroid; rcIntersect.Intersect(m_rcShipPos);
    BitmapData bmData = m_bmShip.LockBits(
                               new Rectangle(rcIntersect.X - m_rcShipPos.X, 
                                             rcIntersect.Y - m_rcShipPos.Y, 
                                             rcIntersect.Width, rcIntersect.Height), 
                               ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
    BitmapData bmData2 = m_bmPlanets.LockBits(
                      new Rectangle(87 * a.nBitmap + rcIntersect.X - a.rcAsteroid.X, 
                                   rcIntersect.Y - a.rcAsteroid.Y, 
                                   rcIntersect.Width, rcIntersect.Height), 
                                   ImageLockMode.ReadOnly, 
                                   PixelFormat.Format24bppRgb);

    int stride = bmData.Stride;
    System.IntPtr Scan0 = bmData.Scan0;
    System.IntPtr Scan1 = bmData2.Scan0;

    unsafe
    {
        byte * p = (byte *)Scan0;
        byte * p1 = (byte *)Scan1;

        int nOffset = stride - rcIntersect.Width*3;

        for(int y=0;y<rcIntersect.Height;++y)
        {
            for(int x=0; x < rcIntersect.Width; ++x )
            {
                if (p[0] != 0 &&
                    p[1] != 0 &&
                    p[2] != 0 &&
                    p1[0] != 0 &&
                    p1[1] != 0 &&
                    p1[2] != 0)
                {
                    m_bmShip.UnlockBits(bmData);
                    m_bmPlanets.UnlockBits(bmData2);
                    return true;
                }

                p += 3;
                p1 += 3;;
            }
            p += nOffset;
            p1 += nOffset;
        }
    }

    m_bmShip.UnlockBits(bmData);
    m_bmPlanets.UnlockBits(bmData2);

    return false;            
}

So now we do our rectangle test first, then test this function, and if we get a true for both, we end the game.  A message box informs us of our score, and then when we close that, the game starts again, with the star field regenerated in a new pattern.

It's not very exciting ( the target audience was my 5 year old daughter ), but it achieved my personal goal of giving me an excuse to write some more C# code, and hopefully the topics I've covered along the way have been of interest to some fellow CPians.  I have an Asteroids game using DirectX on my hard drive somewhere, I will dig it up and write something about it so that a comparison can be made.  It's hardly a fair one, given that this stuff is not what C# is for, and I don't know that C++ without DirectX would have fared that much better.  I'd say C# made this easier to write by virtue of the integration of GDI+ which did a lot of the work for us. 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Christian Graus
Software Developer (Senior)
Australia Australia
Programming computers ( self taught ) since about 1984 when I bought my first Apple ][. Was working on a GUI library to interface Win32 to Python, and writing graphics filters in my spare time, and then building n-tiered apps using asp, atl and asp.net in my job at Dytech. After 4 years there, I've started working from home, at first for Code Project and now for a vet telemedicine company. I owned part of a company that sells client education software in the vet market, but we sold that and I worked for the owners for five years before leaving to get away from the travel, and spend more time with my family. I now work for a company here in Hobart, doing all sorts of Microsoft based stuff in C++ and C#, with a lot of T-SQL in the mix.

Comments and Discussions

 
GeneralMy vote of 5 PinmemberSri Harsha Chilakapati10-Feb-13 3:24 
GeneralCheers PinmemberPaul Evans29-Oct-03 12:04 
GeneralCollision Pinmemberhayat24-May-03 14:23 
GeneralAnother idea PinmemberJames T. Johnson18-Apr-02 13:53 
GeneralSpeed enhancement PinmemberJames T. Johnson18-Apr-02 8:54 
GeneralRe: Speed enhancement PinmemberChristian Graus18-Apr-02 8:57 
GeneralRe: Speed enhancement PinmemberJames T. Johnson18-Apr-02 9:03 
GeneralRe: Speed enhancement PinmemberChristian Graus18-Apr-02 9:12 
GeneralRGB vs BGR PinmemberBlake Coverett17-Apr-02 22:04 
GeneralRe: RGB vs BGR PinmemberChristian Graus17-Apr-02 22:16 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140415.2 | Last Updated 17 Apr 2002
Article Copyright 2002 by Christian Graus
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid