Problems with both alpha and AABB collision

,

Hi there! I've been struggling with some collision problems in my game. I'm doing all my testing on 2.1.0 now, but I also saw this behavior in 2.0.3. After many hours of debugging, I decided to set up a separate, reduced test case. I'm still seeing the same issues there, so it seemed like time to file a bug report. Hopefully I'm just making some silly mistakes, but at this point I'm pretty sure there's something deeper going on.

In short, there seems to be some unexpected behavior with both the alpha and AABB collision detection methods provided by the SDK. Specifically:

Alpha behavior

The pixel content of the images being compared seems to change the result of alpha collision methods (i.e. playdate.graphics.checkAlphaCollision and playdate.graphics.sprite:alphaCollision) in unexpected ways.

There seem to be many things that can trigger this unexpected behavior. The simplest reproduction I've found is a full white or black image vs one with one (any?) clear pixel. In this gif, note how the pixel perfect collisions work as expected between the arrow and right hand square with one clear pixel (shown by the top right red text).

rightsprite.2023-10-31 19_24_10

Collisions between the arrow and fully white square on the left are strange. They seem to work mostly normally against the top/bottom edges of the square. At the left/right sides, though, no alpha collision is reported until there's significant overlap.

leftsprite.2023-10-31 19_31_51

It's difficult for me to tell exactly what's happening, but the lack of alpha collision reported seems to have something to do with the amount of vertical overlap the arrow has with the square? Note:

  1. How the trigger depth differs depending on the direction I approach from.
  2. The arrow sprite can be rotated with the crank (this is done via imagetable swap), and the trigger depth is symmetrical/consistently weird. The arrow sprite used makes no difference for alpha collision against the image with one clear pixel.
  3. Which pixel is clear doesn't seem to matter, just that some pixel is clear.

My game is using playdate.graphics.checkAlphaCollision and tilemaps. I also set up the repro app that way initially (which you can see as of commit a1bee162). However, the same issue can be reproduced with just normal sprites and the built in :alphaCollision. This is much simpler (and removes a bunch of my code/math from the equation), so I updated the example.

AABB behavior

This one seems like generally a less funky problem, but I'm still stumped. Note how the collCount in the top right is reported as 1 correctly when I overlap each sprite individually. However, overlapping both at the same time still only shows 1:

onlyone.2023-10-31 19_17_48


:sweat_smile: Thanks for reading all that, hopefully it's not a waste of time. AND, thanks for making Playdate! It's been an absolute joy to build on.

1 Like

Thank you for the detailed post and the test project, that makes looking at stuff like this a lot easier!

The second issue - AABB behavior - is explained by the fact that :checkCollisions() behaves just like :moveWithCollisions() without actually moving the sprite. Since your player sprite does not have a collision response type set, it defaults to kCollisionTypeFreeze. So, when it collides with one of the box sprites it stops moving and doesn't "collide" with the second.

To detect all collisions you would need to add sprite.collisionResponse = gfx.sprite.kCollisionTypeOverlap to your Player:_initSprite() function, OR use playdate.graphics.sprite.allOverlappingSprites() instead.

As for the Alpha Behavior issue, I agree it seems like something odd is going on there, but it's going to take some more investigation to figure out what exactly is going on. The code path is different for images with an alpha mask, so it makes some sense that the weirdness doesn't exist in the sprite using the image with the clear pixel.

As a workaround while until we can get this fixed, adding a mask to the image seems to fix the issue:

local image = tileImages:getImage(1)
image:addMask()
local spr1 <const> = gfx.sprite.new(image)
2 Likes

Wonderful, thanks for the quick reply! Of course you're correct about the kCollisionTypeFreeze default. After reading your post, I realized I'm already setting kCollisionTypeOverlap in my main project, and just missed it in the repro :see_no_evil:

The different code path for alpha masked images makes total sense. Using :addMask() as you described seems to be a fantastic workaround. Really appreciate your help!