moveWithCollisions cannot collide with the same object twice

OS X, SDK 1.9.1

I'm not 100% sure this is a bug, or intended behavior, as to actually demonstrate it my example is kind of contrived.

What I think is happening, is when using moveWithCollisions and having a sprite bounce off other sprites – a sprite will only bounce off another particular sprite once.

I demonstrate this by putting a "ball" close to a wall and attempting to moveWithCollisions beyond the wall:

    local actualX, actualY, cols, cols_len = ballSprite:moveWithCollisions(X+30, Y)

With just the one wall, things behave as expected... the ball bounces off and lands further to the left.

However, if I place another wall:


And move by the same amount:

the ball ends up on the other side of the wall. Now... it's not that it ignored the wall altogether... based on the distance moved it seems to have bounced off the right wall, bounced off the left wall, and then gone through and ignored the right wall after the first two bounces.

Here is a demo of a more dynamic example:
jump over a wall
Here the ball is bouncing back and forth at a slow speed... then I bump the speed up (enough that it would hit the two walls more than once on one-step, and it escapes the wall cage).

Lastly the origins of this problem are a pong style game I describe here:
[Pongdate - two-player Pong built with the SDK]
escapeVelocity
The solution here is just to have a max speed, but it got me curious if there was an underlying bug that could affect sprites in a more intended scenario.

Here's zip files of my couple of tests:
collisiontest.zip (22.3 KB)
collisiontest2.zip (1.9 KB)

You're using kCollisionTypeBounce, and I think what may be happening is that when the bounce is big enough, it can bounce into overlapping the opposite wall, and then the next moveWithCollisions() call doesn't notice a collision because the sprites were already overlapping at the start of the movement.

Changing the collision type to kCollisionTypeFreeze seems to fix it.

I tweaked it slightly to make sure it wasn't overlapping at the very start, and it still doesn't behave as expected.

Using Freeze isn't the correct behavior though – it should bounce between those walls.

I guess what I'm saying in all this, is I don't believe the sprite collision framework behaves correct if/when a sprite collides twice with the same other sprite in one moveWithCollisions, which can happen when kCollisionTypeBounce is the desired behavior.

Cheers!

1 Like

I agree with @jestelle, this sounds like a bug to me.
I also noticed it in the SpriteCollisionMasks example when you enable collisions between every object.
It can be seen, from time to time, that a sudden and unexpected jump/warp of one of the sprites happens.
Given enough time, the sprites on the screen will decrease due to them breaking the screen boundaries.

2 Likes

I can (finally!) confirm that this is a bug. I don't have a fix yet but I did make a nice little tester app to make it easier to visualize:

collision bug

If anyone is curious, here is the code for the tester app (it's kind of fun to play with!):

import 'CoreLibs/graphics'
import 'CoreLibs/sprites'

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

local wall1 = gfx.sprite.new()
wall1:setBounds(300, 50, 10, 150)
wall1:setCollideRect(0, 0, 10, 150)
wall1:add()
function wall1:draw(x, y, width, height)
	gfx.fillRect(x, y, width, height)
end
local wall2 = gfx.sprite.new()
wall2:setBounds(150, 190, 150, 10)
wall2:setCollideRect(0, 0, 150, 10)
wall2:add()
function wall2:draw(x, y, width, height)
	gfx.fillRect(x, y, width, height)
end
local wall3 = gfx.sprite.new()
wall3:setBounds(140, 70, 10, 130)
wall3:setCollideRect(0, 0, 10, 150)
wall3:add()
function wall3:draw(x, y, width, height)
	gfx.fillRect(x, y, width, height)
end


local distance = 300
local angle = 110
local playerOrigin = geo.point.new(225, 170)


local player = gfx.sprite.new()
player:setSize(10, 10)
player:setCollideRect(0, 0, 10, 10)
player:moveTo(playerOrigin)
player.collisionResponse = gfx.sprite.kCollisionTypeBounce
player:add()

function player:draw(x, y, w, h)
	gfx.fillCircleInRect(x, y, w, h)
end


local function positionForAngle(length, angle)
	angle = (angle + 270) % 360 -- rotate -90° so that 0° is north instead of east
	local rad = math.rad(angle)
	local x = length * math.cos(rad)
	local y = length * math.sin(rad)
	return x, y
end


function playdate.update()
	gfx.clear()
	
	
	if playdate.buttonIsPressed( playdate.kButtonUp ) then
		playerOrigin:offset(0, -2)
	end
	if playdate.buttonIsPressed( playdate.kButtonRight ) then
		playerOrigin:offset(2, 0)
	end
	if playdate.buttonIsPressed( playdate.kButtonDown ) then
		playerOrigin:offset(0, 2)
	end
	if playdate.buttonIsPressed( playdate.kButtonLeft ) then
		playerOrigin:offset(-2, 0)
	end
	
	
	player:moveTo(playerOrigin)
	gfx.sprite.update()
	
	local dx, dy = positionForAngle(distance, angle)
	gfx.drawLine(player.x, player.y, player.x + dx, player.y+dy)
	gfx.drawCircleAtPoint(player.x + dx, player.y+dy, 5)
	
	-- draw midpoints
	gfx.setPattern({ 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55 })
	local step = 12
	for i = step, distance, step do
		player:moveTo(playerOrigin)
		local dx, dy = positionForAngle(i, angle)
		actualX, actualY, _, _ = player:moveWithCollisions(player.x + dx, player.y+dy)
		gfx.drawCircleAtPoint(actualX, actualY, 5)
	end
	
	player:moveTo(playerOrigin)
	actualX, actualY, collisions, length = player:moveWithCollisions(player.x + dx, player.y+dy)
	gfx.drawText("Collision Count: " .. length, 10, 210)
	gfx.fillCircleAtPoint(actualX, actualY, 5)
	gfx.setColor(gfx.kColorBlack)
	gfx.drawCircleAtPoint(actualX, actualY, 5)
	
	gfx.drawText("Crank to adjust adjust distance", 10, 10)
	gfx.drawText("A + Crank to adjust angle", 10, 30)
	gfx.drawText("D-pad to move starting point", 10, 50)
	
	gfx.drawText("Distance: " .. string.format("%.1f", distance), 10, 170)
	gfx.drawText("Angle: " .. string.format("%.1f", angle), 10, 190)
end


function playdate.cranked(change)
	
	if playdate.buttonIsPressed( playdate.kButtonA ) then	
		angle += change
		
	else
	
		distance += change
		if distance < 0 then distance = 0 end
	end
end
1 Like

I just wanted to mention that there is a fix waiting to be released, hopefully to be included in the next SDK verison.

1 Like