Potential Bug with SDK collisions: Collision-type Slide + Corner

Hi all,

I wanted to report this bug (or limitation, perhaps) with the SDK collisions system.

Context

The behavior was being reported causing a strange bug in my game. When standing by the top-right or top-left corner of a block, the player was not able to move horizontally above that same block, unlike what was expected from a normal ground/slide collision.

Here is an example of the bug from the game:

horizontal-block-bug
You can see that while pressing the "Left" key the player is not able to move left.

(...and yes, the player also has the left key :D)

The Bug

With a bit of digging I found that the bug is caused by the SDK collisions and the way it calculates the "Slide" vector.

When the bug occurs, there are two (important) collisions happening:

  • The first collision is with the "corner block", since the player's movement vector is pointing both left (horizontal movement) and down (gravity)
  • The second collision is with the wall below the player, again due to the downwards movement simulating gravity.

Both have collision type Slide.

The issue is that with the first movement, given that the exact location of the player's bottom-left coincides with the "corner block"'s top-right, the SDK collision system selects only one out of the two directions and the horizontal movement is cancelled. This can be seen with the "normal" of the collision.

The second collision, the "wall", then cancels the vertical movement, leaving the player standing still.

In other words:

  • Step 0: The Player attempts to move bottom-left using SDK collisions
  • Step 1: The "Corner Block" (Wall-2), only keeps vertical movement and sets horizontal movement to 0.
  • Step 2: The bottom "Wall" (Wall-1) sets the vertical movement to 0, there is no horizontal movement to otherwise keep.

I've created the above high-definition diagram to represent my understanding of the bug.

My Work-around

Details

In case you come across this (which I'm sure you won't), what I did was to detect the situation with three variables:

  • isTouchingGround - this already existed for obvious reasons.
  • isHorizontalCornerBlock - detects the horizontal collision in a corner.

Set these variables to false before your checking your collisions, and then for each collision that occurs, check for this edge case scenario.

Before checking collisions:

local isTouchingGround = false
local isHorizontalCornerBlock = false

For each collision:

isTouchingGround = isTouchingGround 
       or  (collision.normal.y == -1 
           and collision.type == playdate.graphics.sprite.kCollisionTypeSlide)

isHorizontalCornerBlock = isHorizontalCornerBlock
       or (collision.normal.x ~= 0
           and collision.type == playdate.graphics.sprite.kCollisionTypeSlide 
           and collision.otherRect.y == collision.spriteRect.y + collision.spriteRect.height)

-- -- You can directly save the reference to the sprite to the variable:
-- if isHorizontalCornerBlock == true then
--     isHorizontalCornerBlock = collision.other
-- end

If both variables are true by the end of the collision check, I disable the collisions for the block in the horizontal corner until the next frame. This will allow the player to move out of the "stuck" position and then the game continues normally.

if isHorizontalCornerBlock and isTouchingGround then
    --print("Found horizontal corner block - disabling collisions.")

    -- Reference to the horizontal corner block
    isHorizontalCornerBlock:setCollisionsEnabled(false)

    -- Schedule reset for collisions
    playdate.frameTimer.new(1,
        function()
            --print("Re-enabled collisions")
            isHorizontalCornerBlock:setCollisionsEnabled(true)
        end)
end

For extra efficiency, you can also add a check for horizontalCornerBlock:collisionsEnabled() to avoid creating multiple timers at once and extract the function out and simply reference it so it only gets created in memory once.

I hope this articulates the "bug" / limitation clearly. Crossing fingers I'm not the only who experiences this or that this helps someone :slight_smile:

In case it would be helpful, I'm available to put together a minimum-code demo so don't hesitate to reach out!