Setting non-integer widths for sprite collisionRect bounds causes erratic collision response

I noted this behavior when implementing tile collisions for an isometric game. The game uses empty sprites with collideRects set to simulate collisions. The resulting object positions are then converted from the collision system's coordinate space to screen space (isometric).

On game start I generate the empty collision sprites for all of the walls in the tilemap. The size of the tiles in this coordinate system are non-integer since they are calculated from pixel sizes of the isometric tile art. I noticed that for many of the walls I would get erratic behavior when colliding against certain walls. Here is an example of one instance:

Note that this occurs for different faces depending on the position of the collider. For some rows of tiles it only happens for the bottom face. For other rows it's the bottom and right faces.

I never saw the behavior when colliding with the top or left face of a collider.

I was able to resolve this by calling math.floor() on the width and height of the collisionBounds for the walls.

(Assuming this issue is confirmed) Ideally, this could be resolved within bump.lua, however I'd settle for a little note in the docs mentioning that the bounds width and height should be integers for best results. :slight_smile:

Either way, perhaps this will save someone else three hours of debugging.

I've created a minimal example (without all the isometric nonsense) which shows the behavior:

  1. Turn on "Show Sprite Collision Rects"
  2. Use the arrow keys to collide with the bottom face of the box
  3. Move left, right, up, or down and note how the box jumps past the edge of the other collider in the opposite direction of the movement direction
import 'CoreLibs/graphics'
import 'CoreLibs/sprites'

local gfx <const> = playdate.graphics
local geo <const> = playdate.geometry

local player
local collider
local velocity = geo.vector2D.new(0, 0)

function load()
  player = gfx.sprite.new()
  player:setSize(64, 64)
  player:setCenter(0.5, 0.5)
  player:setCollideRect(25, 25, 39, 39)
  player:setCollidesWithGroups({ 1 })
  player:moveTo(20, 20)
  player:add()

  collider = gfx.sprite.new()
  collider:setCenter(0, 0)
  collider:setSize(46, 46)
  collider:setCollideRect(0, 0, 46.66905, 46.66905)
  -- Uncommenting the following line resolves the issue.
  -- collider:setCollideRect(0, 0, math.floor(46.66905), math.floor(46.66905))
  collider:setGroups({ 1 })
  collider:moveTo(46.66905, 93.3381)
  collider:add()
end

function playdate.update()
  local movement = geo.vector2D.new(0, 0)

  if playdate.buttonIsPressed( playdate.kButtonUp ) then
    movement.y = -1
  end
  if playdate.buttonIsPressed( playdate.kButtonRight ) then
    movement.x = 1
  end
  if playdate.buttonIsPressed( playdate.kButtonDown ) then
    movement.y = 1
  end
  if playdate.buttonIsPressed( playdate.kButtonLeft ) then
    movement.x = -1
  end

  velocity = movement * 5

  local targetX, targetY = player.x + velocity.x, player.y + velocity.y

  player:moveWithCollisions(targetX, targetY)

  gfx.sprite.update()
end

load()
3 Likes