Basic Per Pixel Collision

From TRCCompSci - AQA Computer Science
Jump to: navigation, search

Introduction

This will build on the rectangle bounds collision method. Per Pixel collision ignores the transparent background of either character, this is done by checking every pixel which overlaps on both characters. If one or both are transparent then those two pixels haven't collided. Below is an example of per pixel collision:

Per pixel.gif

Characters

I have declared the following Textures & 2 vectors to control the position of these. I have also added two Color arrays to store the colours within the player & enemy:

        Texture2D enemy;
        Texture2D player;
        Vector2 ppos, epos;
        Color[] playerTextureData;
        Color[] enemyTextureData;

In reality your project will probably create a class for player and a class for enemy. This will then include the texture, position and so on for your character. For simplicity and for the fear of giving you a complete player class definition, or to give you a complete collision detection method, I will show you how to check for a collision between two object by creating bounding rectangles and checking for an intersection.

In the Initialize method for the game I have set the position vectors for my player and enemy:

            ppos = new Vector2(0, 0);
            epos = new Vector2(300, 300);
            // Set a constant player move speed
            playerMoveSpeed = 8.0f;

LoadContent Section

You will need to add the following lines to the LoadContent section:

           // Extract collision data
            enemyTextureData = new Color[enemy.Width * enemy.Height];
            enemy.GetData(enemyTextureData);
            playerTextureData = new Color[player.Width * player.Height];
            player.GetData(playerTextureData);

The code above will create an array of the colour of every pixel in both textures. The enemyTextureData is first given a size, and then the GetData method is used to populate the array with the appropriate values.

New Method

Create a new method in your Game1.cs:

static bool IntersectPixels(Rectangle rectangleA, Color[] dataA, Rectangle rectangleB, Color[] dataB)

In the new method add the following lines:

            // Find the bounds of the rectangle intersection
            int top = Math.Max(rectangleA.Top, rectangleB.Top);
            int bottom = Math.Min(rectangleA.Bottom, rectangleB.Bottom);
            int left = Math.Max(rectangleA.Left, rectangleB.Left);
            int right = Math.Min(rectangleA.Right, rectangleB.Right);

top, bottom, left, and right will create a rectangle of the intersecting textures. What we now need to do is to check the colour data for this intersecting rectangle. If either colours are transparent then it is not a collision, but if both the player texture colour and the enemy texture colour aren't transparent then a collision has occurred:


            // Check every point within the intersection bounds
            for (int y = top; y < bottom; y++)
            {
                for (int x = left; x < right; x++)
                {
                    // Get the color of both pixels at this point
                    Color colorA = dataA[(x - rectangleA.Left) + (y - rectangleA.Top) * rectangleA.Width];
                    Color colorB = dataB[(x - rectangleB.Left) + (y - rectangleB.Top) * rectangleB.Width];

                    // If both pixels are not completely transparent,
                    if (colorA.A != 0 && colorB.A != 0)
                    {
                        // then an intersection has been found
                        return true;
                    }
                }
            }

Player Movement

I have already created the basic player movement:

            if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
                ButtonState.Pressed || Keyboard.GetState().IsKeyDown(
                Keys.Escape))
                Exit();

            // store position before movement
            Vector2 oldpos = ppos;

            // Save the previous state of the keyboardso we can determine single key presses
            previousKeyboardState = currentKeyboardState;

            // Read the current state of the keyboard and store it
            currentKeyboardState = Keyboard.GetState();

            if (currentKeyboardState.IsKeyDown(Keys.Left))
            {
                ppos.X -= playerMoveSpeed;
            }

            if (currentKeyboardState.IsKeyDown(Keys.Right))
            {
                ppos.X += playerMoveSpeed;
            }

            if (currentKeyboardState.IsKeyDown(Keys.Up))
            {
                ppos.Y -= playerMoveSpeed;
            }

            if (currentKeyboardState.IsKeyDown(Keys.Down))
            {
                ppos.Y += playerMoveSpeed;
            }

This is from the Update method. It will allow your player to move around, you can't have collisions if you can't move. Oldpos is used to store the position before any movement, we can use this to undo a movement if we collide.

Create Player Rectangle

// Get bounds of enemy
            Rectangle playerRectangle = new Rectangle((int)ppos.X, (int)ppos.Y, player.Width, player.Height);

The (int) is an example of casting, this will convert the value ppos.X to an integer. The rectangle points are its X & Y (ie top right corner), and then the width and height are used to get the bottom left corner.

Create Enemy Rectangle

// Get bounds of enemy
            Rectangle enemyRectangle = new Rectangle((int)epos.X, (int)epos.Y, enemy.Width, enemy.Height);

This is the same as above except using the enemy.

Checking if they overlap

Now we have the 2 rectangles you can use these and the colour data to check for an intersection. This use the new method you created above:

// Get bounds of enemy
if (IntersectPixels(playerRectangle, playerTextureData, enemyRectangle, enemyTextureData))
            {
                 A COLLISION HAS TAKEN PLACE AND YOUR PROGRAM SHOULD DO SOMETHING
            }

My example altered the movement values changed before the collision detection:

 if (IntersectPixels(playerRectangle, playerTextureData, enemyRectangle, enemyTextureData))
            {
                 ppos = oldpos;
            }