Enter
Enter

Reputation: 87

Monogame - Per Pixel Collision on iOS

For first, I want to say, that I'm using this code for per pixel collision detection:

    public bool CollidesWith(Sprite other)
    {
        // Default behavior uses per-pixel collision detection
        return CollidesWith(other, true);
    }

    public bool CollidesWith(Sprite other, bool calcPerPixel)
    {
        // Get dimensions of texture
        int widthOther = other.Texture.Width;
        int heightOther = other.Texture.Height;
        int widthMe = Texture.Width;
        int heightMe = Texture.Height;

        if (calcPerPixel &&                                // if we need per pixel
            ((Math.Min(widthOther, heightOther) > 10) ||  // at least avoid doing it
            (Math.Min(widthMe, heightMe) > 10)))          // for small sizes (nobody will notice :P)
        {
            return Rectangle.Intersects(other.Rectangle) // If simple intersection fails, don't even bother with per-pixel
                && PerPixelCollision(this, other);
        }

        return Rectangle.Intersects(other.Rectangle);
    }

    public bool PerPixelCollision(Sprite a, Sprite b)
    {
        // Get Color data of each Texture
        Color[] bitsA = new Color[a.Texture.Width * a.Texture.Height];
        a.Texture.GetData(bitsA);
        Color[] bitsB = new Color[b.Texture.Width * b.Texture.Height];
        b.Texture.GetData(bitsB);

        // Calculate the intersecting rectangle
        int x1 = Math.Max(a.Rectangle.X, b.Rectangle.X);
        int x2 = Math.Min(a.Rectangle.X + a.Rectangle.Width, b.Rectangle.X + b.Rectangle.Width);

        int y1 = Math.Max(a.Rectangle.Y, b.Rectangle.Y);
        int y2 = Math.Min(a.Rectangle.Y + a.Rectangle.Height, b.Rectangle.Y + b.Rectangle.Height);

        // For each single pixel in the intersecting rectangle
        for (int y = y1; y < y2; ++y)
        {
            for (int x = x1; x < x2; ++x)
            {
                // Get the color from each texture
                Color a1 = bitsA[(x - a.Rectangle.X) + (y - a.Rectangle.Y) * a.Texture.Width];
                Color b1 = bitsB[(x - b.Rectangle.X) + (y - b.Rectangle.Y) * b.Texture.Width];

                if (a1.A != 0 && b1.A != 0) // If both colors are not transparent (the alpha channel is not 0), then there is a collision
                {
                    return true;
                }
            }
        }
        // If no collision occurred by now, we're clear.
        return false;
    }

this code is in my "Sprite" struct. It works fine on Windows, OpenGL (MonoGame Cross Platform Desktop Project), Android and on Windows UWP.

But on iOS when I call the Sprite.CollidesWith(...); my program stops rendering screen. Anything works but it can't render new frames (it renders static image). (I added click sound to test if program crashed. (after freeze when I click screen, then i can hear the sound))

Problem is on this line:

a.Texture.GetData(bitsA);

I searched the web and i found that Texture2D.GetData<> is not implemented on iOS... soo, is there any possibility to make pixel-perfect collision detection on iOS? (without calling GetData<>)

Sorry for any mistakes, I'm new to this forum. And thanks for any help.

Upvotes: 0

Views: 230

Answers (1)

craftworkgames
craftworkgames

Reputation: 9957

I searched the web and i found that Texture2D.GetData<> is not implemented on iOS...

So here's the thing. Texture2D.GetData is not intended to be used the way you're using it. What it actually does is read texture data back out of the GPU after the texture has already been sent to the graphics card. That's not something you want to be doing if you can avoid it.

Have a read of this thread on the MonoGame forums if you don't believe me. Here's a quote from that thread that sums it up nicely.

We need to put this in big letters in the documentation. Don't call GetData() every frame. Avoid it entirely if possible. GPUs are designed for data to go into them, not back the other way.

On most platforms this is a fairly slow operation. On some platforms, as you've discovered, it's not even possible.


soo, is there any possibility to make pixel-perfect collision detection on iOS? (without calling GetData<>)

So the real question here is how do you avoid using GetData at all. As mentioned in the comments there's already a pretty reasonable workaround you could use if you want something quick and dirty.

Saving or reading files may affect game performance.

I find it a little ironic that you're worried about game performance when you're already killing rendering performance with per-pixel collisions.

The fact is, most games don't do per-pixel collision detection for performance reasons. It's almost certainly not going to be noticeable to the players if you used a simpler collision shapes like rectangles and circles. I think it's worth considering if you actually need them.

I don't know what kind of game you're making though. Some games do need per-pixel collisions, so if that is indeed the case I'll give you my thoughts on how to approach it.

I think the best approach the doing per-pixel collisions is to store the texture data in some kind of binary format. But more than that, ideally, you'd want to pre-process the data through the MonoGame Content Pipeline or something so that you're not reading the data from a slow source at runtime. An approach like this would give you the best of both worlds (fast loading at game startup and also fast rendering).

It's a lot of work, but it might be something to think about.

Upvotes: 3

Related Questions