-
|
|
Pixel-By-Pixel Hit Detection
|
Hello!
I used the pixel-by-pixel tutorial and added it to a game, I am still relatively new to C# and XNA but i've picked it up quite well so far. The hit detection worked a treat, but myself and a friend are making a platformer and with any platformer there are several 'platforms' or in this case walkways.
It was working a treat, but it wasn't scalable. Each time I wanted to add a new walkway I would need to create a new image, load it in, load the content... generate the variables... add more hitTests... and because I had to use a separate texture each time there was lots of code that could be easily looped.
So I decided to create a 1x8 image, then use a method to generate the walkways, they appear perfectly as I wanted them too. But then when the hitTests were tried it threw an "Out if index" error on the line:
| |
| Color colorB = dataB[(x - rectangleB.Left) + |
| (y - rectangleB.Top) * rectangleB.Width]; |
Which I'm fairly sure is because when I get the set Color[] array I use the 1x8px texture and the actual walkway is 600x8 :)
I know there is a work-a-round, well I assume there is, but I have no idea what it is. Below is my source code, I just hope someone has the answer! :)
The two methods that are used, first to detect collisions and the second to generate the walkways!
| |
| static bool collisionDetection(Rectangle rectangleA, Color[] dataA, |
| Rectangle rectangleB, Color[] dataB) |
| { |
| // 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); |
| |
| 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; |
| } |
| } |
| } |
| |
| // No intersection found |
| return false; |
| } |
| |
| public void generateWalkway(int walkwayID, int walkwayX, int walkwayY, int walkwayWidth) |
| { |
| walkwayPos[walkwayID].X = walkwayX; |
| walkwayPos[walkwayID].Y = walkwayY; |
| walkwayRect[walkwayID] = new Rectangle((int)walkwayPos[walkwayID].X, (int)walkwayPos[walkwayID].Y, walkwayWidth, 8); |
| } |
The variable declaration:
| |
| static int totalWalkways = 2; |
| |
| Texture2D singleWalkwayTexture; |
| Color[] singleWalkwayTextureData; |
| |
I then call the method in the Initialize part:
| |
| generateWalkway(0, 0, 600, 600); |
| generateWalkway(1, 0, 500, 750); |
Then I set the Color[] array
| |
| singleWalkwayTextureData = new Color[singleWalkwayTexture.Width * singleWalkwayTexture.Height]; |
| singleWalkwayTexture.GetData(singleWalkwayTextureData); |
Then finally I generate the collision detection:
| |
| for (int c = 0; c < totalWalkways; c++) |
| { |
| if (collisionDetection(playerRectangle, personTextureData, |
| walkwayRect[c], singleWalkwayTextureData)) |
| { |
| isJumping = false; |
| inContactWithWalkway = true; |
| } |
| else |
| { |
| inContactWithWalkway = false; |
| } |
| } |
I hope it all made sense, that's all the code I used to do with the walkways and hit detections etc :)
Thanks in advance,
Ashley
|
|
-
|
|
Re: Pixel-By-Pixel Hit Detection
|
I think you are better off just using standard Rectangles for platformer collisions. They are far more efficient and I think a lot easier to implement. And you don't have to mess around with textures and content. Just define the rectangles using an editor or simply type them straight into notepad or an xml file.
Game hobbyist hell-bent on coding a diabolical Matrix
|
|
-
|
|
Re: Pixel-By-Pixel Hit Detection
|
Maybe so, but you have to learn one day, so I might as well learn how to do it now :)
Besides I want to code it as cleanly as possible as this is the cleanest way with very little excess code :)
|
|
-
|
|
Re: Pixel-By-Pixel Hit Detection
|
the cleanest way it to use rectangles and the built in "Intersects" Method. An xml file with all platforms then some parsing logic to make those useable would be the best way to do this.
|
|
-
|
|
Re: Pixel-By-Pixel Hit Detection
|
I did use rectangles to begin with but I want this to be professional and as accurate as possible, when using rectangles the hitTests were far from clean or precise. I really want to do it this way so if anyone has any help or something constructive to add, that would be great :)
My point still stands, I have to learn sometime.
|
|
-
|
|
Re: Pixel-By-Pixel Hit Detection
|
Ashley iS H4X:I did use rectangles to begin with but I want this to be professional and as accurate as possible, when using rectangles the hitTests were far from clean or precise. I really want to do it this way so if anyone has any help or something constructive to add, that would be great :)
My point still stands, I have to learn sometime.
To get more precise collisions with rectangles, you simply define more granular rectangles.
Yes you do have to learn sometime, but my thinking is you are learning the wrong thing for the wrong application. Pixel perfect collison testing is good for some situations, but testing for platform collisions in a platormer game is not one of them imo. So you might actually be doing your learning some harm, because it looks like you are already having to hack an implementation to force it to work the way you want, which is not the way it will work when you really need it for the correct situation.
Also note, for professional/accurate platformer collision testing, you don't create the platforms from the collision data, or vice versa. The platform layout and the collision data are typically 2 separate data structures and usually unrelated. This allows for far better flexibility.
Game hobbyist hell-bent on coding a diabolical Matrix
|
|
-
|
|
Re: Pixel-By-Pixel Hit Detection
|
Craig Martin:Yes you do have to learn sometime, but my thinking is you are learning the wrong thing for the wrong application. Pixel perfect collison testing is good for some situations, but testing for platform collisions in a platormer game is not one of them imo.
quoted for truth
|
|
-
|
|
Re: Pixel-By-Pixel Hit Detection
|
Well you seem to know what you're talking about, and maybe I should do it that way, but every programmer has their own style and thought patterns and this makes the most sense to me. So if you'd be so kind as to aid me that would be really nice :)
|
|
-
-
- (2473)
-
premium membership
-
Posts
724
|
Re: Pixel-By-Pixel Hit Detection
|
I'm going to agree with what is being said here. I began my platformer by trying to implement pixel-perfect collision, but a little reading and testing showed me I was approaching it the wrong way. I suppose it depends on the type of platformer you're creating, but unless it's something like Worms with destructable, pixel-perfect backgrounds, I'd go with rectangles.
|
|
-
|
|
Re: Pixel-By-Pixel Hit Detection
|
But using a pure rectangle1.intersects(rectangle2) is really inaccurate and sometimes the two sprites can be a few pixels apart and still register as a hit and it just looks really poorly programmed. I'm thinking of entering the imagine cup, and they would expect far more than a simple intersects method and they would expect it to look professional etc.
Ashley
|
|
-
|
|
Re: Pixel-By-Pixel Hit Detection
|
If you want something more accurate than rectangles, you could use polygons or line segments or something similar to those. Polygons can be good for platformers that aren't tile-based.
|
|
-
|
|
Re: Pixel-By-Pixel Hit Detection
|
Maybe this isn't the ideal game to impliment such a hit detection system in, but i'm betting one day I will need to know and if anyone does know, i'd love to know as I hate not knowing how to do something!
|
|
-
|
|
Re: Pixel-By-Pixel Hit Detection
|
Here is how to use what you have without getting exceptions. Hope this helps.
Ask me if you want anything clarified.
If you want to test it add your own images .tga format for alpha.
| 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 HitTest |
| { |
| /// <summary> |
| /// This is the main type for your game |
| /// </summary> |
| public class Game1 : Microsoft.Xna.Framework.Game |
| { |
| GraphicsDeviceManager graphics; |
| SpriteBatch spriteBatch; |
| public Texture2D person; |
| Texture2D walkway; |
| Rectangle personRect; |
| Rectangle walkwayRect; |
| Color[] walkwayColors; |
| Color[] personColors; |
| bool hitB; |
| Texture2D hit; |
| Rectangle hitRect; |
| |
| public Game1() |
| { |
| graphics = new GraphicsDeviceManager(this); |
| Content.RootDirectory = "Content"; |
| } |
| |
| /// <summary> |
| /// Allows the game to perform any initialization it needs to before starting to run. |
| /// This is where it can query for any required services and load any non-graphic |
| /// related content. Calling base.Initialize will enumerate through any components |
| /// and initialize them as well. |
| /// </summary> |
| protected override void Initialize() |
| { |
| // TODO: Add your initialization logic here |
| |
| base.Initialize(); |
| } |
| |
| /// <summary> |
| /// LoadContent will be called once per game and is the place to load |
| /// all of your content. |
| /// </summary> |
| protected override void LoadContent() |
| { |
| // Create a new SpriteBatch, which can be used to draw textures. |
| spriteBatch = new SpriteBatch(GraphicsDevice); |
| person = Content.Load<Texture2D>("person"); |
| walkway = Content.Load<Texture2D>("walkway"); |
| hit = Content.Load<Texture2D>("hit"); |
| |
| personRect = new Rectangle(0, 0, person.Width, person.Height); |
| walkwayRect = new Rectangle(graphics.GraphicsDevice.Viewport.Width / 2, graphics.GraphicsDevice.Viewport.Height / 2, walkway.Width, walkway.Height); |
| hitRect = new Rectangle(0, 0, hit.Width, hit.Height); |
| |
| personColors = new Color[person.Width * person.Height]; |
| person.GetData(personColors); |
| walkwayColors = new Color[walkway.Width * walkway.Height]; |
| walkway.GetData(walkwayColors); |
| // TODO: use this.Content to load your game content here |
| } |
| |
| /// <summary> |
| /// UnloadContent will be called once per game and is the place to unload |
| /// all content. |
| /// </summary> |
| protected override void UnloadContent() |
| { |
| // TODO: Unload any non ContentManager content here |
| } |
| |
| /// <summary> |
| /// Allows the game to run logic such as updating the world, |
| /// checking for collisions, gathering input, and playing audio. |
| /// </summary> |
| /// <param name="gameTime">Provides a snapshot of timing values.</param> |
| protected override void Update(GameTime gameTime) |
| { |
| // Allows the game to exit |
| if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) |
| this.Exit(); |
| MouseState state = Mouse.GetState(); |
| |
| personRect.X = state.X; |
| personRect.Y = state.Y; |
| // TODO: Add your update logic here |
| hitB = IntersectPixels(personRect, personColors, walkwayRect, walkwayColors); |
| base.Update(gameTime); |
| } |
| static bool IntersectPixels(Rectangle rectangleA, Color[] dataA, |
| Rectangle rectangleB, Color[] dataB) |
| { |
| // 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); |
| |
| // 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) |
| { |
| return true; |
| |
| } |
| } |
| } |
| return false; |
| } |
| /// <summary> |
| /// This is called when the game should draw itself. |
| /// </summary> |
| /// <param name="gameTime">Provides a snapshot of timing values.</param> |
| protected override void Draw(GameTime gameTime) |
| { |
| GraphicsDevice.Clear(Color.CornflowerBlue); |
| spriteBatch.Begin(SpriteBlendMode.AlphaBlend); |
| if (hitB) |
| { |
| spriteBatch.Draw(hit, hitRect, Color.White); |
| } |
| |
| spriteBatch.Draw(person, personRect, Color.White); |
| spriteBatch.Draw(walkway, walkwayRect, Color.White); |
| spriteBatch.End(); |
| // TODO: Add your drawing code here |
| |
| base.Draw(gameTime); |
| } |
| } |
| } |
| |
|
|
-
|
|
Re: Pixel-By-Pixel Hit Detection
|
My apologies to Ashley, sometimes I get too zealous if I think someone is going down the wrong track ;). But good, it looks like the above poster has helped.
Game hobbyist hell-bent on coding a diabolical Matrix
|
|
-
|
|
Re: Pixel-By-Pixel Hit Detection
|
No worries.
Anyway i've copied that code, shoved it in a new project and weirdly when I load in three images (in .tga format) it claims to not be able to find them... It's the exact same code and structure as the game I have shown above, yet it wont load the images, so I have no way of testing it.
I have studied the code for a while, though, and I cannot notice and single difference that will mean the game will work. All I can see that's different is the fact you set a var when the intersectsPixels() method is called.
I mean there is a hit texture and rectangle, but I cannot understand how this has any relevance or helps. The code is near enough exactly the same with the exception of setting a hit texture and hit rect that is drawn when there is a hit detection. The method code is identical as far as I can tell and the way in which the colours are loaded are identical, the textures are loaded.
I really don't get how that's in anyway a fix for the problem :/
Ashley
|
|
-
|
|
Re: Pixel-By-Pixel Hit Detection
|
If youre going to scale your sprites like that, you'll need to tell the collision detection method the actual dimensions of the texture in order to find a proper pixel in the color array.
I havent actually tested this, but it should work something like it: (its your original method, taking two extra parameters, width of the original textures):
| static bool collisionDetection(Rectangle rectangleA, Color[] dataA, int widthA, |
| Rectangle rectangleB, Color[] dataB, int widthB) |
| { |
| // 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); |
| |
| // calculate height of the original textures |
| int heightA = dataA.Length / widthA; |
| int heightB = dataB.Length / widthB; |
| |
| for (int y = top; y < bottom; y++) |
| { |
| for (int x = left; x < right; x++) |
| { |
| // Get the color of both pixels at this point |
| // trying to take scaling into account here now too... |
| Color colorA = dataA[((x - rectangleA.Left) * (widthA / rectangleA.Width)) + |
| (((y - rectangleA.Top) * rectangleA.Width) * (heightA / rectangleA.Height))]; |
| Color colorB = dataB[((x - rectangleB.Left) * (widthB / rectangleB.Width)) + |
| (((y - rectangleB.Top) * rectangleB.Width) * (heightB / rectangleB.Height))]; |
| |
| // If both pixels are not completely transparent, |
| if (colorA.A != 0 && colorB.A != 0) |
| { |
| // then an intersection has been found |
| return true; |
| } |
| } |
}
| // No intersection found |
| return false; |
| } |
| |
|
|
|
-
|
|
Re: Pixel-By-Pixel Hit Detection
|
Well I am not at my computer now, but I will try it later on.
So the script that JHow posted would not work, but your one should work?
Remembering that I am scaling one image (1x8) so that it spans a greater distance and then hitTesting :)
Thanks,
Ashley
|
|
-
|
|
Re: Pixel-By-Pixel Hit Detection
|
Thankyou! It worked, well, sort of ;)
I have generated two walkways, I have also tried three, but what happens every single time is that say I generate three, the first two I generate will appear, but no collisions will be detected and my player will fall through, but if I go to the last walkway to be generated and land on top of that - success! He stands on the walkway and doesn't fall through...
So the collision detection does work, but only for the last walkway to be generated, here is the code to generate the walkWay is the same as posted previously, and my collision detection for the walkways are here:
totalWalkways is set to three, and I have checked it is being set right - it is definately set to three!
| |
| generateWalkway(0, 0, 600, 600); |
| generateWalkway(1, 0, 500, 750); |
| generateWalkway(2, 0, 400, 900); |
| for (int c = 0; c < totalWalkways; c++) |
| { |
| |
| if (collisionDetection(playerRectangle, personTextureData, playerLeftTexture.Width, |
| walkwayRect[c], singleWalkwayTextureData, singleWalkwayTexture.Width)) |
| { |
| isJumping = false; |
| inContactWithWalkway = true; |
| } |
| else |
| { |
| inContactWithWalkway = false; |
| } |
| } |
I've played around with the code for a while now and i'm clueless once more, it's really really odd that one works and only the last generated one works and the two that are generated first don't work :/
|
|
-
|
|
Re: Pixel-By-Pixel Hit Detection
|
I guess it is because that loop gets run X times, and each iteration is overriding the isContactWithWalkway etc booleans, so even if either of the first two walkways IS in fact colliding with player, third might not be, thus isContactWithwalkway = false when you get out of that loop.
|
|
-
|
|
Re: Pixel-By-Pixel Hit Detection
|
u551:I guess it is because that loop gets run X times, and each iteration is overriding the isContactWithWalkway etc booleans, so even if either of the first two walkways IS in fact colliding with player, third might not be, thus isContactWithwalkway = false when you get out of that loop.
Ah... This is true, clever person! <3
Now I just need a work around, I think the best way would be an array for inContactWithWalkway and then run through the array and if any of them are true then don't let him fall :)
|
|
-
|
|
Re: Pixel-By-Pixel Hit Detection
|
Heh, thats possible but maybe not the best solution. I suggest you just break out of the loop on first positive contact.
|
|
-
|
|
Re: Pixel-By-Pixel Hit Detection
|
Oh, well that would be far simpler haha
Is there a 'proper' way to break out of a loop? I know in Java it's discouraged to 'break out of a loop' and their definitely isn't a method that breaks out of loops. Setting c = totalWalkways works, but I thought i'd check and see if their is a 'proper' way?
Thanks,
Ashley
|
|
-
|
|
Re: Pixel-By-Pixel Hit Detection
|
Ashley iS H4X:
... Setting c = totalWalkways works ....
That seems pretty enough. Im not the man to ask about best practices though.
|
|
-
|
|
Re: Pixel-By-Pixel Hit Detection
|
Game hobbyist hell-bent on coding a diabolical Matrix
|
|
-
|
|
Re: Pixel-By-Pixel Hit Detection
|
Craig Martin:
Thanks, i thought that was only for use in switch statements - I was told it was bad programming to use a break statement within a loop but guess that's fine in C# and obv. not fine in Java haha
|
|
|