XNA Creators Club Online
Page 1 of 1 (8 items)
Sort Posts: Previous Next

SOLVED: Per Pixel Collision Detection of 2D SpriteSheet

Last post 2/19/2009 11:53 PM by NikRadford. 7 replies.
  • 1/13/2009 11:46 PM

    SOLVED: Per Pixel Collision Detection of 2D SpriteSheet

    Recently I used the official XNA 2D per pixel collision checker to replace my rectangle collision checker in my game. Now I find I have an issue with the fact that the XNA collision checker uses single sprites while I use a sprite sheet. Looking all over the internet I have yet to find anyone who has figured out/attempted to figure out a solution to this problem. So here it is:

    I have the code done, it works, but it only works on large images with a lot of pixels strung together. (Solid objects)
    I have a couple of objects that have only 1x1 pixel lines that are the top/bottom of the images. Such as a player controlled lander with thin landing struts (4x1 pixel) and the landing pad (50x1 pixels).
    As I understand it the Color[] ImageData and Image.GetData works by taking all the pixels in a texture image and stringing them out along an X x 1 pixel line and checks each pixel along that line. Unfortunately my sprite sheets are large, about 200X300 pixels or more while my viewable sprites are ~32x32 pixels. So I have this collision checker checking 200X300 pixels with each pass to find out if a single pixel of a 32x32 frame of the sprite sheet has collided with another image. The problem is with only a 1X1 pixel collision point the chance of "missing" a collision check and passing through an object are extremely high and even if I were to "smudge" the numbers my player controlled object would most likely land inside the pad rather than on it.

    For those who need it here's my code.
    Note: I use an OOD that goes (Physics - Sprites- Object - Compiler- Game1) so what you'll see here is the flow of the code, not its actual location in my design scheme:


    //Color data from the image.         
    public Color[] spriteImageData; 
     
    //Collision rectangle for the IntersectsPixel method later. 
    public Rectangle collisionRect 
            { 
                get 
                { 
                    return new Rectangle((int)position.X, (int)position.Y, frameSize.X, frameSize.Y); 
                } 
            } 
     
    //Where the color data is collected from the spriteImage. This is where my code most likely needs to be changed. I'll show an example of something I've tried later in the post. 
    public virtual void LoadContent(GraphicsDevice graphics) 
            { 
                spriteImageData = 
                    new Color[spriteImage.Width  * spriteImage.Height]; 
                    spriteImage.GetData(spriteImageData); 
            } 
     
     
    //IntersectPixels method taken directly from the XNA 2D per pixel collision check. Doesn't need to be changed as far as I can see. 
            private bool IntersectPixels(Rectangle rectangleA, Color[] dataA, Rectangle rectangleB, Color[] dataB) 
            { 
                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); 
     
                for (int y = top; y < bottom; y++) 
                { 
                    for (int x = left; x < right; x++) 
                    { 
                        Color colorA = dataA[(x - rectangleA.Left) + 
                                    (y - rectangleA.Top) * rectangleA.Width]; 
                        Color colorB = dataB[(x - rectangleB.Left) + 
                                    (y - rectangleB.Top) * rectangleB.Width]; 
     
                        if (colorA.A != 0 && colorB.A != 0) 
                        { 
                            return true
                        } 
                    } 
                } 
                return false
            } 
     
    //Check to see if the collision is true and if it is then it changes the collisionState of the objects checked. 
                public override void Update(GameTime gameTime) 
     
                if (IntersectPixels(lander.collisionRect, lander.spriteImageData, 
                    fuelStation.collisionRect, fuelStation.spriteImageData)) 
                { 
                    lander.collisionState = 2; 
                    fuelStation.collisionState = 2; 
                } 
     
                if (IntersectPixels(lander.collisionRect, lander.spriteImageData, new Rectangle(0, 0, screenWidth, screenHeight), foregroundData)) 
                { 
                    if (fuelStation.collisionState == 2) 
                    { 
                        lander.collisionState = 3; 
                    } 
     
                    lander.collisionState = 4; 
                } 
    //All of this code works as advertised. I'm not having crash issues and in-game collision of large objects works superbly, but 1x1 pixel collisions are not possible and I MUST have 1x1 pixel collision or the game will not function correctly. 

    Now I've tried to change how the color data is collected to shorten the collection zone to that of the displayed image from the sprite sheet. Now, although I've gotten the code to work and it doesn't crash the game the texture I "build" is merely a frame that's the same size as the display area and has no color data to collect so no collision is ever detected. Like so:

    protected Texture2D frameTexture; 
     
    frameTexture = new Texture2D(graphics, frameSize.X, frameSize.Y) 
     
                spriteImageData = 
                    new Color[frameSize.X  * frameSize.Y]; 
                    frameTexture.GetData(spriteImageData); 

    If I attempt to smudge the above code by collecting frameSize.X * frameSize.Y and then giving it to "spriteImage" I get an error that says "The size of the data passed in is too large or too small for this resource." which in this case I assume is too small.
    I've thought about lying to the program to make it think that the spriteImage is the same size as the frameSize.X * frameSize.Y, but I have no idea how to convert a Texture2D image into and INT that can then be altered.

    If anyone has any ideas I'd love to hear them.
    I've also posted this on Ziggyware.com and someone mentioned passing the Rectangle argument through the GetData code, but to do that I'd have to turn the Color array into a Texture array and I don't know what that would do to the rest of my code or how it'd interprete the data so that's out unlesss someone can explain to me how to do that.
  • 1/14/2009 5:14 AM In reply to

    Re: SOLVED: Per Pixel Collision Detection of 2D SpriteSheet

    SOLVED!

    Thanks go to ThinkTank from Ziggyware.com for leading me down the right track.

    Here's the new code that only checks the viewable sprite area of the sprite sheet and allows 1x1 pixel collision!

            public virtual void LoadContent(GraphicsDevice graphics) 
            { 
                spriteImageData = new Color[spriteImage.Width * spriteImage.Height]; 
                spriteImage.GetData(0,  
                    new Rectangle(currentFrame.X * frameSize.X, currentFrame.Y * frameSize.Y, frameSize.X, frameSize.Y),  
                    spriteImageData,  
                    currentFrame.X * currentFrame.Y,  
                    frameSize.X * frameSize.Y); 


    As you can see it's a little chunky, but this returns the mipmap level (0 since my sprites are not scaled or rotated), the rectangle size which is the same as my drawing rectangle size, where to send the color data, the current start point of the data collection and the total number of pixels in the data collection area.

    All of the area and pixel calculations are the same as the draw area calculations so the code repeats itself a bit. But this is how you can use sprite sheets and per pixel collision detection together!

    If you need to know more on how this all interacts with my current OOD scheme feel free to ask.

    Otherwise, Enjoy!

  • 2/12/2009 6:39 PM In reply to

    Re: SOLVED: Per Pixel Collision Detection of 2D SpriteSheet

    Hi there,
    First of all, i want to apologize for my bad english.
    I'll do all I can to be clear.

    I try to integrate your GetData Line, but i've some problem to understood all of this.

    My problem is that all times i use this line :
    spriteImage.GetData(0, new Rectangle(currentFrame.X * frameSize.X, currentFrame.Y * frameSize.Y, frameSize.X, frameSize.Y), spriteImageData,  currentFrame.X * currentFrame.Y,  frameSize.X * frameSize.Y);  
    Instead of :
    player.GetData(playerTextureData); 
    My program just can't compile with a problem of index or somethng like this.

    My code is the next :
    (Problem is between //////////)
    using System; 
    using System.Collections.Generic; 
    using System.Linq; 
    using Microsoft.Xna.Framework; 
    using Microsoft.Xna.Framework.Audio; 
    using Microsoft.Xna.Framework.Content; 
    using Microsoft.Xna.Framework.GamerServices; 
    using Microsoft.Xna.Framework.Graphics; 
    using Microsoft.Xna.Framework.Input; 
    using Microsoft.Xna.Framework.Media; 
    using Microsoft.Xna.Framework.Net; 
    using Microsoft.Xna.Framework.Storage; 
     
    namespace X
        class Player 
        { 
            Texture2D player; 
            Texture2D player_up; 
            Texture2D player_down; 
            Texture2D player_left; 
            Texture2D player_right; 
            Texture2D player_idle; 
     
            Vector2 playerpos; 
            public Color[] playerTextureData; 
            Color playercolor; 
            public Rectangle PlayerRectangle; 
     
            PlayerIndex playernumber; 
     
            public bool isInGame = false
     
            float Timer = 0f; 
            float Interval = 1000f / 10f; 
            int FrameCount = 5; 
            int CurrentFrame = 0; 
            Rectangle SourceRect; 
            Rectangle DestRect; 
     
           Constructor here but you don't care 
     
            public void LoadContent(IServiceProvider serviceProvider) 
            { 
                content = new ContentManager(serviceProvider, "Content"); 
     
     
    THIS IS SPRITESHEETS 
                player_idle = this.content.Load<Texture2D>("frog_idle"); 
                player_down = content.Load<Texture2D>("frog_down"); 
                player_up = content.Load<Texture2D>("frog_up"); 
                player_left = content.Load<Texture2D>("frog_left"); 
                player_right = content.Load<Texture2D>("frog_right"); 
                player = content.Load<Texture2D>("frog_idle"); 
              
            } 
     
            public void Update(GameTime gameTime) 
            { 
                GamePadState GState = GamePad.GetState(playernumber); 
     
                if (GState.ThumbSticks.Left.X < 0) 
                { 
                    playerpos.X = playerpos.X + (GState.ThumbSticks.Left.X*3); 
                    player = player_left; 
                    TimerSprite(gameTime, 42, 48, 42, 48); 
                } 
                if (GState.ThumbSticks.Left.X > 0) 
                { 
                    playerpos.X = playerpos.X + (GState.ThumbSticks.Left.X*3); 
                    player = player_right; 
                    TimerSprite(gameTime, 42, 48, 42, 48); 
                } 
                if (GState.ThumbSticks.Left.Y < 0) 
                { 
                    playerpos.Y = playerpos.Y - (GState.ThumbSticks.Left.Y*3); 
                    player = player_down; 
                    TimerSprite(gameTime, 40, 48, 40, 48); 
                } 
                if (GState.ThumbSticks.Left.Y > 0) 
                { 
                    playerpos.Y = playerpos.Y - (GState.ThumbSticks.Left.Y * 3); 
                    player = player_up; 
                    TimerSprite(gameTime, 36, 48, 36, 48); 
                } 
     
                if (GState.ThumbSticks.Left.X == 0 && GState.ThumbSticks.Left.Y == 0) 
                { 
                    player = player_idle; 
                    TimerSprite(gameTime, 32, 48, 32, 48); 
                } 
     
                PlayerRectangle = new Rectangle((int)playerpos.X, (int)playerpos.Y, player.Width, player.Height); 
            } 
     
            public void Draw(GameTime gameTime, SpriteBatch spriteBatch) 
            { 
                DrawPerso(gameTime, player, spriteBatch); 
            } 
     
     
            void TimerSprite(GameTime gameTime, int SpriteWidth, int SpriteHeight, int ImgWidht, int ImgHeight) 
            { 
                Timer += (float)gameTime.ElapsedGameTime.TotalMilliseconds; 
                if (Timer > Interval) 
                { 
                    CurrentFrame++; 
                    if (CurrentFrame > FrameCount - 1) 
                    { 
                        CurrentFrame = 0; 
                    } 
                    Timer = 0f; 
                } 
     
     
    /////////////////////////////////////////////////////////////// 
                SourceRect = new Rectangle(CurrentFrame * SpriteWidth, 0, SpriteWidth, SpriteHeight); 
                DestRect = new Rectangle((int)playerpos.X, (int)playerpos.Y, ImgWidht, ImgHeight); 
                 
                playerTextureData = new Color[player.Width * player.Height]; 
     
                player.GetData(0, new Rectangle(DestRect.Width * SourceRect.Width, DestRect.Height * SourceRect.Height, SourceRect.Width, SourceRect.Height), playerTextureData, DestRect.Width * DestRect.Height, SourceRect.Width * SourceRect.Height);  
            } 
    /////////////////////////////////////////////////////// 
            void DrawPerso(GameTime gametime, Texture2D txtPerso, SpriteBatch spriteBatch) 
            { 
        
                spriteBatch.Draw(txtPerso, DestRect, SourceRect, Color.White); 
     
            } 
        } 




    I use the same Intersect fonction that in the 2D pixel intersect tutorial.

    And to control my collision :
     if (Collision.IntersectPixels(player3.PlayerRectangle, player3.playerTextureData, 
                                                  player4.PlayerRectangle, player4.playerTextureData)) 
                    { 
                        text = "collision spot"
                        GamePad.SetVibration(PlayerIndex.One, (float)0.1, (float)0.1); 
                        GamePad.SetVibration(PlayerIndex.Two, (float)0.1, (float)0.1); 
                    } 
                    else 
                    { 
                        text = "no collision"
                        GamePad.SetVibration(PlayerIndex.One, (float)0.0, (float)0.0); 
                        GamePad.SetVibration(PlayerIndex.Two, (float)0.0, (float)0.0); 
                    } 


    Thanks in advance.

    Best Regards.
    Maldus
  • 2/14/2009 11:45 AM In reply to

    Re: SOLVED: Per Pixel Collision Detection of 2D SpriteSheet

    Sorry to re-post on this thread, but i'm still searching a better solution (or a working solution ^^) to do collision with spritesheet !

    So if someone can explain me what can i do to solve my problem, because it's the only thing that i don't know how to do...

    Thanks.

    Best Regards
    Maldus.
  • 2/15/2009 5:47 PM In reply to

    Re: SOLVED: Per Pixel Collision Detection of 2D SpriteSheet

    I finally manage to make it work but in another way.

    If someone need it, ask me ;)

    Maldus.
    Best Regards.

  • 2/16/2009 11:40 PM In reply to

    Re: SOLVED: Per Pixel Collision Detection of 2D SpriteSheet

    Yeah, please post it. There must be a way of getting the array for the current frame only, and iterating that, rather than iterating that in the entire sheet.
  • 2/17/2009 12:53 AM In reply to

    Re: SOLVED: Per Pixel Collision Detection of 2D SpriteSheet

    I use another fonction :

            public static bool Intersects(Player a, Player b) 
            { 
                if (Collision.Intersects(a.PlayerRectangle, b.PlayerRectangle)) 
                { 
     
                    uint[] bitsA = new uint[a.player.Width * a.player.Height]; 
                    a.player.GetData<uint>(bitsA); 
     
                    uint[] bitsB = new uint[b.player.Width * b.player.Height]; 
                    b.player.GetData<uint>(bitsB); 
     
                    int x1 = Math.Max(a.PlayerRectangle.X, b.PlayerRectangle.X); 
                    int x2 = Math.Min(a.PlayerRectangle.X + a.PlayerRectangle.Width, b.PlayerRectangle.X + b.PlayerRectangle.Width); 
     
                    int y1 = Math.Max(a.PlayerRectangle.Y, b.PlayerRectangle.Y); 
                    int y2 = Math.Min(a.PlayerRectangle.Y + a.PlayerRectangle.Height, b.PlayerRectangle.Y + b.PlayerRectangle.Height); 
     
                    for (int y = y1; y < y2; ++y) 
                    { 
                        for (int x = x1; x < x2; ++x) 
                        { 
                            if (((bitsA[(x - a.PlayerRectangle.X) + (y - a.PlayerRectangle.Y) * a.PlayerRectangle.Width] & 0xFF000000) >> 24) > 20 && 
                                ((bitsB[(x - b.PlayerRectangle.X) + (y - b.PlayerRectangle.Y) * b.PlayerRectangle.Width] & 0xFF000000) >> 24) > 20) 
                                return true
                        } 
                    } 
                } 
     
                return false
            } 

    I draw the playerRectangle by enter the size of sprite and his position.
    After, if this two rectangle collides, i see if there is some pixel that are over  20 in alpha that collide
    .

    Maldus.
  • 2/19/2009 11:53 PM In reply to

    Re: SOLVED: Per Pixel Collision Detection of 2D SpriteSheet

    Color[] myData = new Color[20 * 20]; texture.GetData( 0, //Mip map level, for standard sprites this will always be 0 new Rectangle(0, 0, 20, 20), // The section of the sprite that you want to retrieve myData, // The array to put it in 0, // Index offset of the array to start copying into myData.Length // The number of elements you want to copy ); Just so you know.
Page 1 of 1 (8 items) Previous Next