My suggestions (Throbax TD works the same way, but is a RTS game) is that you first register the sprites to test for collisions on a grid. Choose the size reasonably. Read (or use) this to get an idea of what I’m talking about.
Now that you have a reasonably low number of collision-candidates you can do per-pixel collision testing.
Get the currently drawn texture from the billboard for the two sprites and their position and test for the collision.
Here is some example code from our game:
/// Determines if there is overlap of the non-transparent pixels between two
/// <param name="transformA">World transform of the first sprite.</param>
/// <param name="widthA">Width of the first sprite's texture.</param>
/// <param name="heightA">Height of the first sprite's texture.</param>
/// <param name="dataA">Pixel color data of the first sprite.</param>
/// <param name="transformB">World transform of the second sprite.</param>
/// <param name="widthB">Width of the second sprite's texture.</param>
/// <param name="heightB">Height of the second sprite's texture.</param>
/// <param name="dataB">Pixel color data of the second sprite.</param>
/// True if non-transparent pixels overlap; <c>false</c> otherwise
public static bool IntersectPixels(Matrix transformA, int widthA, int heightA, bool dataA, Matrix transformB,
int widthB, int heightB, bool dataB)
if (dataB == null)
// Calculate a matrix which transforms from A's local space into
// world space and then into B's local space.
Matrix bInverted = Matrix.Invert(transformB);
Matrix transformAtoB = transformA*bInverted;
// When a point moves in A's local space, it moves in B's local space with a
// fixed direction and distance proportional to the movement in A.
// This algorithm steps through A one pixel at a time along A's X and Y axes
// Calculate the analogous steps in B:
Vector2 stepX = Vector2.TransformNormal(Vector2.UnitX, transformAtoB);
Vector2 stepY = Vector2.TransformNormal(Vector2.UnitY, transformAtoB);
// Calculate the top left corner of A in B's local space.
// This variable will be reused to keep track of the start of each row.
Vector2 yPosInB = Vector2.Transform(Vector2.Zero, transformAtoB);
// For each row of pixels in A.
for (int yA = 0; yA < heightA; yA++)
// Start at the beginning of the row.
Vector2 posInB = yPosInB;
// For each pixel in this row.
for (int xA = 0; xA < widthA; xA++)
// Round to the nearest pixel.
int xB = (int) Math.Round(posInB.X);
int yB = (int) Math.Round(posInB.Y);
// If the pixel lies within the bounds of B.
if (0 <= xB && xB < widthB && 0 <= yB && yB < heightB)
// Get the colors of the overlapping pixels.
bool colorA = dataA[xA + yA*widthA];
bool colorB = dataB[xB + yB*widthB];
// If both pixels are not completely transparent,
if (colorA && colorB)
// then an intersection has been found.
// Move to the next pixel in the row.
posInB += stepX;
// Move to the next row.
yPosInB += stepY;
// No intersection found.
And that’s how we call it:
Matrix transformA = Matrix.Identity;
transformA *= Matrix.CreateTranslation(-Origin.X, -Origin.Y, 0f);
transformA *= Matrix.CreateScale(new Vector3(Scale.X, Scale.Y, 1f));
transformA *= Matrix.CreateRotationZ(RotationalCorrectionInRadian + Rotation);
transformA *= Matrix.CreateTranslation(Position.ToVector3());
Matrix transformB = Matrix.Identity;
transformB *= Matrix.CreateTranslation(-otherSprite.Origin.X, -otherSprite.Origin.Y, 0f);
transformB *= Matrix.CreateScale(new Vector3(otherSprite.Scale.X, otherSprite.Scale.Y, 1f));
transformB *= Matrix.CreateRotationZ(otherSprite.RotationalCorrectionInRadian + otherSprite.Rotation);
transformB *= Matrix.CreateTranslation(otherSprite.Position.ToVector3());
if (Utils.IntersectPixels(transformA, spriteTexture.SpriteWidth, spriteTexture.SpriteHeight,
GetCurrentSpriteCollisionData(spriteTexture), transformB, otherSpriteTexture.SpriteWidth,
The only problem could be the Color we pass to the method. This is something we pre-calculate in our content-processor. You would have to change it to a texture.
If you’re interested or need further help, I will refactor it and put it up on github or MonoGame.Extended (if it doesn’t already contain that).