Page 1 of 1

Tile based collision, player catches on corners of tiles despite lack of downward velocity

Posted: Sun Jan 16, 2022 10:47 pm
by rando
I'm trying to make a tile based game, and I've been trying to make this collision system. What it does is it takes the player's velocity, checks for collisions between where it is and where it will be, and adjusts the velocity to prevent collision. It's almost working perfectly, but there's a bug that I cannot find the cause of. When the player slides on a row of tiles, they get caught at every corner. They have absolutely no downward velocity, so the issue is not that the tiles are in the wrong order. There's gotta be something wrong with the maths. Another weird thing is that this happens almost every time, yet there will be an extremely rare occasion where this never happens. I have no idea why this might be. Here is the collision code:

Code: Select all

function Entity:collide(e)
	local r = {}
	r.ret = false
	
	if self.vx == 0 and self.vy == 0 then return r end
	
	local expandedTarget = Entity(e.x - self.width / 2, e.y - self.height / 2)
	expandedTarget.width = self.width + e.width
	expandedTarget.height = self.height + e.height
	
	r = self:rayVsRect(expandedTarget)
	
	if r.ret == false then return r end
	if r.time < 1 and r.time >= 0 then r.ret = true else r.ret = false end
	
	return r
end

function Entity:rayVsRect(e)
	local r = {}
	r.ret = false
	
	local ray = {}
	ray.ox = self.x + self.width / 2
	ray.oy = self.y + self.height / 2
	ray.mx = self.vx
	ray.my = self.vy
	
	local tNear = {}
	tNear.x = (e.x - ray.ox) / ray.mx
	tNear.y = (e.y - ray.oy) / ray.my
	
	local tFar = {}
	tFar.x = (e.x + e.width - ray.ox) / ray.mx
	tFar.y = (e.y + e.height - ray.oy) / ray.my
	
	if tNear.x > tFar.x then tNear.x, tFar.x = tFar.x, tNear.x end
	if tNear.y > tFar.y then tNear.y, tFar.y = tFar.y, tNear.y end
	
	if tNear.x > tFar.y or tNear.y > tFar.x then return r end
	
	local tHitNear = math.max(tNear.x, tNear.y)
	local tHitFar = math.min(tFar.x, tFar.y)
	
	if tHitFar < 0 then return r end
	
	r.cpX = ray.ox + tHitNear * ray.mx
	r.cpY = ray.oy + tHitNear * ray.my
	
	if tNear.x > tNear.y then
		if ray.mx < 0 then
			r.cnX = 1
			r.cnY = 0
		else
			r.cnX = -1
			r.cnY = 0
		end
	elseif tNear.y > tNear.x then
		if ray.my < 0 then
			r.cnX = 0
			r.cnY = 1
		else
			r.cnX = 0
			r.cnY = -1
		end
	else
		if ray.mx < 0 then
			r.cnX = 1
		else
			r.cnX = -1
		end
		if ray.my < 0 then
			r.cnY = 1
		else
			r.cnY = -1
		end
	end
	
	r.time = tHitNear
	r.ret = true
	
	return r
end
Any and all help is greatly appreciated.

Re: Tile based collision, player catches on corners of tiles despite lack of downward velocity

Posted: Mon Jan 17, 2022 12:32 am
by pgimeno
Can you give a full runnable example? Doesn't need to be your project, doesn't need to have graphics, just rectangles will do.

Re: Tile based collision, player catches on corners of tiles despite lack of downward velocity

Posted: Mon Jan 17, 2022 3:12 am
by rando
pgimeno wrote: Mon Jan 17, 2022 12:32 am Can you give a full runnable example? Doesn't need to be your project, doesn't need to have graphics, just rectangles will do.
Here you go:
AABB Swept (1).zip
(2.88 KiB) Downloaded 63 times

Re: Tile based collision, player catches on corners of tiles despite lack of downward velocity

Posted: Mon Jan 17, 2022 2:48 pm
by pgimeno
Thanks for the test case. I haven't looked in enough detail, but when the problem is happening, you are dividing 0/0.

When the ray starts at the edge of the box (for example when e.x - ray.ox = 0) and the movement is parallel to the edge (ray.mx = 0), the division returns NaN and the algorithm is not prepared to handle this case gracefully. In particular, NaN is not equal, less or greater than anything, so all comparisons will fail. I have not analysed the consequences of this, though.

But one solution is to detect this and return no collision in this case.

Code: Select all

        -- insert this after setting tFar.x and tFar.y:
        if tNear.x ~= tNear.x or tNear.y ~= tNear.y or tFar.x ~= tFar.x 
                or tFar.y ~= tFar.y then return r end

Re: Tile based collision, player catches on corners of tiles despite lack of downward velocity

Posted: Mon Jan 17, 2022 6:56 pm
by rando
pgimeno wrote: Mon Jan 17, 2022 2:48 pm Thanks for the test case. I haven't looked in enough detail, but when the problem is happening, you are dividing 0/0.

When the ray starts at the edge of the box (for example when e.x - ray.ox = 0) and the movement is parallel to the edge (ray.mx = 0), the division returns NaN and the algorithm is not prepared to handle this case gracefully. In particular, NaN is not equal, less or greater than anything, so all comparisons will fail. I have not analysed the consequences of this, though.

But one solution is to detect this and return no collision in this case.

Code: Select all

        -- insert this after setting tFar.x and tFar.y:
        if tNear.x ~= tNear.x or tNear.y ~= tNear.y or tFar.x ~= tFar.x 
                or tFar.y ~= tFar.y then return r end
Thank you so much! The error now is that the player only gets caught when moving downwards and to the left across the tiles, but I already know that this is because of the order the tiles are checked in. I just need to sort the order of checking tiles based on the player's velocity, and I will fully implement that when I make a tilemap rather than individual tiles.