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

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Post Reply
rando
Prole
Posts: 5
Joined: Mon Feb 01, 2021 9:48 pm

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

Post 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.
:neko:
User avatar
pgimeno
Party member
Posts: 3541
Joined: Sun Oct 18, 2015 2:58 pm

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

Post 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.
rando
Prole
Posts: 5
Joined: Mon Feb 01, 2021 9:48 pm

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

Post 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 62 times
:neko:
User avatar
pgimeno
Party member
Posts: 3541
Joined: Sun Oct 18, 2015 2:58 pm

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

Post 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
rando
Prole
Posts: 5
Joined: Mon Feb 01, 2021 9:48 pm

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

Post 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.
:neko:
Post Reply

Who is online

Users browsing this forum: No registered users and 8 guests