Hi all!
Reporting a bug I found while debugging for someone on the Playdate Squad Discord (thread here).
I found that the gfx.checkAlphaCollision seems to return wrong values when given a sprite more than 1px tall (in height). The same did not apply to sprites more than 1px in width.
Here you can see a sprite that is 1 pixel large, with the expected behavior:

(Screen inverts on collision)
Here's another example with a horizontal line, also working:

And, here are two instances of the bug. Both are more than 1px tall vertically.


In the meantime I wrote a fix which bypasses the bug, by splitting the method to only check horizontal slices of an image. There's definitely a performance loss involved but it can be useful if someone needs it:
Workaround method
---comment
---@param image1 _Image
---@param x1 integer
---@param y1 integer
---@param isFlip1 integer
---@param image2 _Image
---@param x2 integer
---@param y2 integer
---@param isFlip2 integer
local function _alphaCollisionFixed(image1, x1, y1, isFlip1, image2, x2, y2, isFlip2)
-- Alpha collision check individually for each line on y-axis
local w1, h1 = image1:getSize()
local w2, h2 = image2:getSize()
local imageToCheck = gfx.image.new(w2, 1)
local numLines = math.abs(y2 + h2 - (y1 + h1))
local isAlphaCollision = false
for i = 0, numLines - 1 do
-- Draw sub-image
gfx.pushContext(imageToCheck)
gfx.clear(gfx.kColorClear)
image2:draw(0, -i)
gfx.popContext()
isAlphaCollision = isAlphaCollision or
gfx.checkAlphaCollision(image1, x1, y1, isFlip1, imageToCheck, x2, y2 + i, isFlip2)
end
return isAlphaCollision
end
For reproducing the bug, here is the main.lua:
main.lua
import "CoreLibs/object"
import "CoreLibs/graphics"
import "CoreLibs/sprites"
import "CoreLibs/timer"
local w = playdate.display.getWidth()
local h = playdate.display.getHeight()
local gfx <const> = playdate.graphics
local function drawMine(x, y, s, a)
local am = a * math.pi / 180
gfx.fillPolygon(x + s * math.cos(am), y + s * math.sin(am), x + s * math.cos(am + math.pi * 2 / 5),
y + s * math.sin(am + math.pi * 2 / 5), x + s * math.cos(am + math.pi * 2 * 2 / 5),
y + s * math.sin(am + math.pi * 2 * 2 / 5), x + s * math.cos(am + math.pi * 3 * 2 / 5),
y + s * math.sin(am + math.pi * 3 * 2 / 5), x + s * math.cos(am + math.pi * 4 * 2 / 5),
y + s * math.sin(am + math.pi * 4 * 2 / 5))
end
local function gen_rect_field(n, r)
local gen_a = {}
local gen_r = {}
local gen_p = {}
for i = 1, n do
gen_a[i] = math.random(0, 360)
gen_r[i] = r * r * r - math.random(1, r) * math.random(1, r) * math.random(1, r)
gen_p[i] = math.random(0, 360)
if gen_r[i] < 10 or gen_r[i] > r * r * r - 10 then
gen_r[i] = r * r * r - 10
end
end
return gen_a, gen_r, gen_p
end
local function d2r(d)
return math.pi * d / 180
end
local mine_a, mine_r, mine_p = gen_rect_field(100, 6)
local function render_minefield(gx, gy, ga)
local c = math.cos(d2r(ga))
local s = math.sin(d2r(ga))
local xmod = c * gx - s * gy
local ymod = s * gx + c * gy
for i = 1, 100 do
local x = math.cos(d2r(mine_a[i] + ga)) * mine_r[i] + xmod + w / 2
local y = math.sin(d2r(mine_a[i] + ga)) * mine_r[i] + ymod + h / 2
--local x = math.cos(d2r(mine_a[i]) * mine_r[i] + math.cos(d2r(ga)) * gx + math.sin(d2r(ga)) * gy
--local y = math.sin(d2r(mine_a[i] + ga)) * mine_r[i] + w / 2 + math.sin(d2r(ga)) * gx + math.cos(d2r(ga)) * gy
if x + 10 > 0 and x - 10 < w and y + 10 > 0 and y - 10 < h then
drawMine(x, y, 10, mine_p[i] + ga)
end
end
end
local imageBackground <const> = gfx.image.new(w, h, gfx.kColorClear)
local imagePlayer <const> = gfx.image.new(7, 1.618 * 7, gfx.kColorWhite)
local imagePixel <const> = gfx.image.new(1, 1, gfx.kColorWhite)
local imageHLine <const> = gfx.image.new(4, 1, gfx.kColorWhite)
local imageVLine <const> = gfx.image.new(1, 4, gfx.kColorWhite)
local crankPosition = 0
local xCrankPosition, yCrankPosition
local xMinefield = 0
local yMinefield = 0
---comment
---@param image1 _Image
---@param x1 integer
---@param y1 integer
---@param isFlip1 integer
---@param image2 _Image
---@param x2 integer
---@param y2 integer
---@param isFlip2 integer
local function _alphaCollisionFixed(image1, x1, y1, isFlip1, image2, x2, y2, isFlip2)
-- Alpha collision check individually for each line on y-axis
local w1, h1 = image1:getSize()
local w2, h2 = image2:getSize()
local imageToCheck = gfx.image.new(w2, 1)
local numLines = math.abs(y2 + h2 - (y1 + h1))
local isAlphaCollision = false
for i = 0, numLines - 1 do
-- Draw sub-image
gfx.pushContext(imageToCheck)
gfx.clear(gfx.kColorClear)
image2:draw(0, -i)
gfx.popContext()
isAlphaCollision = isAlphaCollision or
gfx.checkAlphaCollision(image1, x1, y1, isFlip1, imageToCheck, x2, y2 + i, isFlip2)
end
return isAlphaCollision
end
function playdate.update()
crankPosition = playdate.getCrankPosition()
xCrankPosition = math.cos(crankPosition * math.pi / 180)
yCrankPosition = math.sin(crankPosition * math.pi / 180)
yMinefield += xCrankPosition
xMinefield += yCrankPosition
-- render the bg of the screen
gfx.pushContext(imageBackground)
gfx.clear(gfx.kColorClear)
render_minefield(xMinefield, yMinefield, crankPosition)
gfx.popContext()
gfx.clear(gfx.kColorBlack)
imageBackground:invertedImage():draw(0, 0)
-- Render player, check alpha collisions
local xPlayer, yPlayer = math.floor((w - 7) / 2), math.floor((h - 7 * 1.618) / 2)
--------------------------------------------------------
--- PARAMS TO CUSTOMIZE
--------------------------------------------------------
-- local imageToDraw = imagePixel -- Working
-- local imageToDraw = imageHLine -- Working
-- local imageToDraw = imageVLine -- Broken
local imageToDraw = imagePlayer -- Broken
-- local fnAlphaCollision = _alphaCollisionFixed -- Working
local fnAlphaCollision = gfx.checkAlphaCollision -- Broken for height > 1
--------------------------------------------------------
--------------------------------------------------------
imageToDraw:draw(xPlayer, yPlayer)
if fnAlphaCollision(imageBackground, 0, 0, 0, imageToDraw, xPlayer, yPlayer, 0) then
playdate.display.setInverted(true)
else
playdate.display.setInverted(false)
end
end
You can easily swap out the different example images to test the behavior. I also included the option to test with the alpha-collisions-fix work-around method.
Happy debugging! ![]()
