Pixel-Perfect Collision Detection

Topics: Sprites and SpriteBatch
Dec 7, 2013 at 6:46 PM
I've been looking at this example online for implementing pixel-perfect collision detection (currently for cocos2d-x) in Cocos2d-XNA:

http://blog.muditjaju.infiniteeurekas.in/?p=1

It uses OpenGL fragment shaders and the glReadPixels method to do collision detection. So far, I haven't found a way to access OpenGL methods through Cocos2d-XNA. I have found the CCUtils.GetGLExtensions that returns a list of strings with the extension names, but I'm not sure how (or if) they can be accessed. If anyone has an example or other suggestions, that would be great. Thanks.
Coordinator
Dec 7, 2013 at 6:50 PM
hello

There's no way to get at the GL direct calls without hacking the cocos2d-xna and MonoGame code.

XNA provides an API for getting the texture data from a Texture2D, but that's not the smartest way to do collision detection.

you should preprocess your textures and use the CCMaskSprite to do the collision detection. We provided a binary collision detection algorithm for general use, but it can be easily ported to more complex collisions.

Otherwise, consider using box2d to manage your physics collisions.
Dec 9, 2013 at 6:39 PM
Edited Dec 9, 2013 at 6:42 PM
Thanks for the reply. I didn't realize CCMaskedSprite existed, so that should be most helpful in my collision detection code. :) After looking at the code and checking out the example in the SpriteMaskTest of the tests.WindowsGL project, it looks like the couple mask files (grossini.mask and ball-hd.mask) in the test project are just text files embedded in the assembly with 0s/1s used to mask the appropriate sprite. A few questions for you:
  • I'm assuming when you said to preprocess my textures, I need some tool to create the mask files for my sprites. Is there a tool out there that creates these .mask files or is that something I need to create myself? I did some Google-Fu and didn't come up with anything for generating .mask files, so I'm guessing this is a format native to cocos2d-xna. I was initially thinking these would be some sort of alpha channel mask that you'd just whip up in your image editor of choice (e.g. Gimp, PhotoShop, etc.) based on your original sprite file. Just looking for a quick way to generate these.
  • What would be the best practice for consuming these masks in my app? Say I make a sprite sheet with numerous frames for animation, I assume I'd need a corresponding mask for each frame. Since they don't appear to be in image format, I'm guessing I can't just make a sprite sheet of the masks in the same position for easy loading.
No flames intended with my questions, just trying to understand how best to generate/consume the masks with CCMaskedSprite. :) If I'm missing something obvious or I've got something wrong above, please correct me. Thanks for your help.
Coordinator
Dec 10, 2013 at 6:30 AM
Hi,

There is a mask generator project that is included in the github source. It's a rudimentary mask generator that can preprocess color masks, or just a binary alpha mask.

Now the topic of masked animations has come up in the office recently. I tried doing a full sheet of masks for Santa Shooter and ended up with a 15 MB mask file. Then I realized that I really don't need a mask for each frame, but rather the key frame for each animation.

You may need one for each frame if you need precise collisions. if so, then you may need to load the masks in each update().

I recommend using embedded resources for the mask files. I rarely use custom content types as it just complicates the build process. Using native formats and embedded resources makes it very easy to port the code over to many platforms quickly.
Dec 24, 2013 at 7:11 PM
Thanks for the reply, that helped a lot. I have successfully implemented a solution that uses the embedded masks in my assembly with CCMaskedSprite instances and have the collision detection code working well. However, in the process, I think I may have uncovered a bug with the CCMaskedSprite.CollidesWith method. From my understanding and looking at the code in the method, a collision should only occur when two bytes from the colliding masks are both one at the same location. While I was testing my code, I noticed that my ship (triangle cone at top) seemed to be disappearing before it reached the enemy ship (a round circle). After drawing the bounding box around the ship & enemy, it seems that as soon as the bounding box of the ship touches a visible part of the enemy sprite, a collision occurs, even though the visible part of the ship sprite is clearly not touching the enemy sprite. It's not a simple bounding box collision because I can overlap the boxes without issues, so long as neither one touches the visible part of the other. I don't see a problem just looking at the method and I haven't had a chance to break through it yet to see if I can spot the issue. I'm going to continue looking at it, but I wondered if you have run into any similar issues. It could be something I'm doing, but I don't think so as it seems pretty straightforward. I'd be happy to ship an example of what I'm seeing if that would help. Any thoughts would be appreciated. Thanks.
Coordinator
Dec 25, 2013 at 1:20 AM
Make sure you are using the version from github or 1.3 from the nuget repository.

Next, the speed of your projectile relative to the update speed of your game loop will determine if the projectile disappears in awkward moments. You may process the collision before the projectile's location is updated and drawn, thus the projectile disappears.

Version 1.3 has many changes to the collision code and has been verified quite a bit.
Dec 25, 2013 at 3:48 AM
Edited Dec 25, 2013 at 3:52 AM
I'm using a compiled version of the latest source from GitHub (pulled it down yesterday), so I should be on the latest version. For the collision test, I was actually moving my ship into the enemy ship, no projectile involved... actually, the projectile looks to work fine, but it doesn't have any transparent areas itself (it's only six pixels, 2x3 mask). I've uploaded a temporary image here to show you what I'm seeing (this may/may not work, the Preview doesn't want to let me try it):

ShipCollision

The bounding boxes intersect without issues, no collision is detected between the ship and enemy ship. However, moving the ship a pixel or two more where the corner of the ship's bounding box hits the enemy ship appears to cause a collision. I have my source checked into GitHub here if that would help seeing the code:

https://github.com/ricke44654/Cocos2D-XNA-Tutorials/tree/master/C2dTutorial3-CollisionDetection

I'm using a grid-based collision detection scheme to eliminate screen areas on the first pass, then I use the CCMaskedSprite.CollidesWith method for bounding box / sprite mask checking. The offending code is in the CollisionGrid.cs file in the CheckCollision method, a snippet of which I'll post here:
                // Only check if there's more than one game object in a grid position
                if (gameObjectList.Count > 1)
                {
                    foreach (var go in gameObjectList)
                    {
                        // Obviously, checking collisions against the same object is right out
                        if (go == sourceObject) continue;

                        // Same type objects don't collide with each other
                        if (go.Type == sourceObject.Type) continue;

                        // Ship and ship bullets can't collide with each other
                        if ((go.Type == GameObjectType.Ship && sourceObject.Type == GameObjectType.ShipBullet) || (go.Type == GameObjectType.ShipBullet && sourceObject.Type == GameObjectType.Ship))
                            continue;

                        // Enemy ships and enemy bullets can't collide with each other
                        if ((go.Type == GameObjectType.Enemy && sourceObject.Type == GameObjectType.EnemyBullet) || (go.Type == GameObjectType.EnemyBullet && sourceObject.Type == GameObjectType.Enemy))
                            continue;

                        // All other collisions are valid, so check for a real collision
                        CCPoint collisionPoint;
                        if (sourceObject.CollidesWith(go, out collisionPoint))
                        {
                            // Let any subscribers know that a collision has occurred
                            if (Collision != null)
                                Collision(this, new CollisionEventArgs(sourceObject, go));
                            return;
                        }
                    }
                }
I could be doing something boneheaded, so if you see something goofy, I'd be happy for any help you have to offer. :) It should run fine, so if you decide to download for a try - arrow keys move the ship, the B key toggles the bounding boxes on/off, the E key toggles enemy ship movement, and the G key toggles the grid on/off. I purposely slowed down the ship movement for testing, so it's easy to get close to an enemy ship. No pressure, just thought I'd offer up the code I'm using as my example if you want to have a look. I'll keep plugging away at it to narrow it further. Thanks for the help.
Coordinator
Dec 25, 2013 at 7:13 PM
What I found when I had problems with the collision code was that the transform was not working properly. Check your transforms by eliminating any rotation and just do a translation collision test. If that still fails, then I bet there is a zero-index error in the collision check. I definitely do not rule out a bug in Cocos2d-XNA.

What I did in Santa Shooter to find these bugs was to show a splatter at the collision point. That immediately identified the bugs that I found in the framework and fixed in 1.3, but there could be more.

so make sure you are scale=1 on both sprites, and rotation = 0. Then we're just testing a translation collision which is easier to debug.
Coordinator
Dec 25, 2013 at 7:17 PM
by the way - thanks for the tutorials, they're fantastic!
Dec 25, 2013 at 8:15 PM
Hmm, that's good to know and I will keep that in mind... I hadn't even thought about rotation/scaling at this point yet. I don't have any rotation or scaling on either sprite, they're being used in their native state. Usually when I run into these things, I try to dumb it down to the basics to pinpoint the issue to eliminate as much code as possible. I'm going to put together a small project with just the ship & enemy, no grid, just simple collision and see what I get. I'll let you know what I find out.

Thanks for the props on the tutorials. I've been having a blast learning about Cocos2d-XNA over the last couple months... it's a really nice framework and it makes it easy to put something fairly powerful together in a short amount of time. Thanks for your hard work on it. I found that I had to pull from a lot of different sources in several languages to get started, and that's made it somewhat difficult to get up to speed. But with a little effort, it hasn't been too bad. I decided to blog about it and create the tutorials to hopefully help others learn from my learning. :)

Thanks for the replies, I'll let you know what I find. Have a merry Christmas.
Coordinator
Dec 26, 2013 at 5:34 AM
Looks like the collision code is inverting the mask and picking the wrong mask location.

when I test it, I see it is pulling the '1' from the bottom of the ship when the collision first occurs at the tip of the ship.
Coordinator
Dec 26, 2013 at 5:38 AM
Try this commit - just pull the current master:

https://github.com/Cocos2DXNA/cocos2d-xna/commit/37e7aa6c796badd169599d971b21ff3f3db215bf

I ran your tutorial against this change and it worked as expected.
Marked as answer by ricke44654 on 12/26/2013 at 8:40 PM
Dec 27, 2013 at 2:43 AM
Indeed, that did fix the problem. It's funny, I was actually getting ready to post on the forum about finding the same thing in my smaller test app when I read your response. :) Thanks very much for the help, I really appreciate it.
Dec 27, 2013 at 4:45 PM
Circling back around to one of your previous comments in this thread... if I am rotating / scaling my masked sprites, does Cocos2d-XNA handle the rotation / scaling on the mask for proper detection using the CollidesWith method (i.e. I don't need separate content/masks for different scales/rotations)? Your response seemed to indicate that it does, but I thought I'd try to eliminate any assumptions I'm making. :)