Collision not being registered

General discussion about LÖVE, Lua, game development, puns, and unicorns.
DannyMcWopper
Prole
Posts: 8
Joined: Tue Nov 14, 2023 4:01 pm

Collision not being registered

Post by DannyMcWopper »

Sometimes bullets go through the walls despite of the fact that both have colliders and bullets are meant to be destroyed once they enter the wall's collider.

Bullets are being added to list_of_bullets {} list on each player or enemy shot and later get updated in love.update()
for i,v in ipairs(list_of_bullets) do
if not v.alive then
table.remove(list_of_bullets, i)
end
v:update(dt)
end


In 99.99% of cases bullets behave correctly according to the behaviour set in function Bullet:new – once they contact a 'static' wall:
self.collider:setPreSolve(function(col, other, contact)
if other.collision_class == 'static' then
self.alive = false
end
end)


Then the first thing that Bullet:update(dt) does is a check whether the bullet is alive and destroys the collider if it is not:
function Bullet:update(dt)
if not self.alive then
self.collider:destroy()


And here is the way bullets and their colliders move after the check for being alive:
self.x = self.x + self.speed * math.cos(self.angle) * dt
self.y = self.y + self.speed * math.sin(self.angle) * dt
self.collider:setPosition(self.x, self.y)
self.collider:setAngle(self.angle)


However sometimes this happens:
image 1 - the bullet is about to hit the wall
image 2 - the bullet is right inside the wall, colliders clearly overlapping
image 3 - the bullet goes outside of the wall and it's collider with no collision detected

Extreme case in in image 4 with all colliders being drawn. Under specific angle all of the bullets just go through.

What surprises me the most is the fact that extremely low speeds for bullets (three seconds to travel from enemy to player) cause almost as much "passes through colliders" as extremely high speeds but relatively high speeds decrease these probability significantly.

Extremely high speeds make bullets travel larger distance than the wall width per frame however the issue with slow speeds seems to be different.

p.s. I was considering raycasting but bullets are supposed to be not so fast, player must have a change to dodge it so I would rather keep drawing colliders for each bullets.

p.s.s. Yes, this is windfield and I know that it is outdated but I am almost on the finish line of the project and changing it would be too painful
Attachments
14.png
14.png (5.48 MiB) Viewed 33394 times
13.png
13.png (2.93 MiB) Viewed 33394 times
12.png
12.png (2.76 MiB) Viewed 33394 times
11.png
11.png (2.75 MiB) Viewed 33394 times
User avatar
pgimeno
Party member
Posts: 3655
Joined: Sun Oct 18, 2015 2:58 pm

Re: Collision not being registered

Post by pgimeno »

Without having looked much, this looks like the typical case of deleting elements from a table that is being iterated. At least the first loop looks like that.

When you're removing an element from a table, all elements after it are renumbered. If you're deleting element 4 in a table of 6 elements, then element 5 becomes 4, and element 6 becomes 5. When i increases, it will check element 5, but the element that was 5 is now 4 and hasn't been checked! So the previous element 5, which is now element 4, isn't checked in this iteration.

To solve it, just iterate in reverse order:

Code: Select all

for i = list_of_bullets, 1, -1 do
  local v = list_of_bullets[i]
  if not v.alive then
    table.remove(list_of_bullets, i)
  end
  v:update(dt)
end
By the way, please use [code] tags instead of [i] tags for code snippets. That will help with clarity.
DannyMcWopper
Prole
Posts: 8
Joined: Tue Nov 14, 2023 4:01 pm

Re: Collision not being registered

Post by DannyMcWopper »

pgimeno wrote: Thu Nov 30, 2023 5:45 pm Without having looked much, this looks like the typical case of deleting elements from a table that is being iterated. At least the first loop looks like that.

When you're removing an element from a table, all elements after it are renumbered. If you're deleting element 4 in a table of 6 elements, then element 5 becomes 4, and element 6 becomes 5. When i increases, it will check element 5, but the element that was 5 is now 4 and hasn't been checked! So the previous element 5, which is now element 4, isn't checked in this iteration.

To solve it, just iterate in reverse order:

Code: Select all

for i = list_of_bullets, 1, -1 do
  local v = list_of_bullets[i]
  if not v.alive then
    table.remove(list_of_bullets, i)
  end
  v:update(dt)
end
By the way, please use [code] tags instead of [i] tags for code snippets. That will help with clarity.
Thank you for advice, but unfortunately this did not fix the issue (and also you are missing '#' sign). Maybe a bit more information could be useful:

Code: Select all

-- bullet.lua

Bullet = Object:extend()

function Bullet:new(x, y, angle, shooter)
	self.image = love.graphics.newImage("bullet.png")
	self.x = x 
	self.y = y 
	self.width = self.image:getWidth() 
	self.height = self.image:getHeight()
	self.angle = angle 
	self.speed = 50
	self.origin_x = self.image:getWidth() / 2
	self.origin_y = self.image:getHeight() / 2
	self.scale = 3
	self.alive = true
	self.shooter = shooter

	self.collider = world:newRectangleCollider(self.x, self.y, self.width*self.scale, self.height*self.scale)
	self.collider:setCollisionClass('bullet')
	self.collider:setObject(self)

	self.collider:setPreSolve(function(col, other, contact)
        if other.collision_class == 'static' then
            self.alive = false
            bullet_hits_wall:play()
            --print("self.alive set to false by PreSolve")
        end
    end)
end
As I said before, low speeds increase the probability of the issue. Here is an example: image 21 with bullets passing through collider while obviously touching it (these bullets get destroyed in collision with the next wall for some reason) and then image 22 with a tiniest step to a side and bullets do not go through anymore. Changing setPreSolve to setPostSolve or using both does nothing since the collision is simply not registered. With the speed of 500 the bullets may still go through the walls but it sometimes takes several minutes to catch one even if enemies shoot one right after another with no 'reloading time'

Here is the part of enemy.lua code that is responsible for creating bullets shot by enemies, but I doubt that the issue is here:

Code: Select all

local player_x, player_y = player.collider:getPosition()
            self.angle = math.atan2(player_y - self.y, player_x - self.x)
            if self.can_shoot then
                local shot_sound = enemy_shot:clone()
                shot_sound:play()
                local bullet_spawn_offset = (self.width * self.scale)
                local enemy_x, enemy_y = self.collider:getPosition()
                local bullet_start_x = enemy_x + math.cos(self.angle) * bullet_spawn_offset
                local bullet_start_y = enemy_y + math.sin(self.angle) * bullet_spawn_offset
                table.insert(list_of_bullets, Bullet(bullet_start_x, bullet_start_y, self.angle, scaler, self))
                self.can_shoot = false
                self.time_since_last_shot = 0
            end
Attachments
22.png
22.png (887.33 KiB) Viewed 33363 times
21.png
21.png (801 KiB) Viewed 33363 times
User avatar
knorke
Party member
Posts: 274
Joined: Wed Jul 14, 2010 7:06 pm
Contact:

Re: Collision not being registered

Post by knorke »

Just some random thoughts...as first step try to figure out what exactly goes wrong.
Is it the collision detection? Or deleting the bullet?
As I said before, low speeds increase the probability of the issue.
Maybe because with low speeds more bullets are alive at the same time? That might point to some error in updating/deleting bullets.
DannyMcWopper
Prole
Posts: 8
Joined: Tue Nov 14, 2023 4:01 pm

Re: Collision not being registered

Post by DannyMcWopper »

knorke wrote: Thu Nov 30, 2023 9:11 pm Just some random thoughts...as first step try to figure out what exactly goes wrong.
Is it the collision detection? Or deleting the bullet?
As I said before, low speeds increase the probability of the issue.
Maybe because with low speeds more bullets are alive at the same time? That might point to some error in updating/deleting bullets.
Deleting the bullets works just fine, it is the collision that is not being detected for some reason. And it is not being detected under some very specific circumstances.

The amount of bullets is not the issue, I have tested that. Adding 15 enemies shooting with almost not reloading time at speed of 500 causes maybe 0.1% of bullets to go through, but even one enemy with speed of 50 and long reloading under specific angle will send almost all if not all of his bullets right through the wall.

I suspect that either windfield or love.physics itself has some issue with handling collisions when angle is involved. Size of colliders is fine, I have made bullets and walls gigantic, taking almost half of the screen, but the issue persisted: specific angles and bullet just goes through the wall.

I was thinking of changing the bullet's collider shape to circle or putting another smaller collider inside the bullet that won't rotate itself. Does that sound reasonable?
User avatar
marclurr
Party member
Posts: 145
Joined: Fri Apr 22, 2022 9:25 am

Re: Collision not being registered

Post by marclurr »

I've had a mess around with Windfield and while I managed get a similar behaviour as you describe, I believe it was result of using the API incorrectly. It would be best if you could share your .love file so we can properly see how your code works altogether.
DannyMcWopper
Prole
Posts: 8
Joined: Tue Nov 14, 2023 4:01 pm

Re: Collision not being registered

Post by DannyMcWopper »

marclurr wrote: Fri Dec 01, 2023 10:21 am I've had a mess around with Windfield and while I managed get a similar behaviour as you describe, I believe it was result of using the API incorrectly. It would be best if you could share your .love file so we can properly see how your code works altogether.
Hopefully I am sharing the project with all the files correctly :)

Best spot for testing is the wall between first room and second enemy, it is easy to find an angle there that lets the bullets to go through the walls.

Currently the speed of bullets is set to slow value, you can change it in bullet.lua, function Bullet:new(x, y, angle, shooter). Currently a lot of bullets go through the values but if you increase the speed to ~400-500 then it gets way better, yet not perfect. Too high speed obviously lets the bullets go through the wall since the bullet travels the distance way longer than the width of the wall. But why reasonable and extremely slow speeds do not register collision, the slower the worse?

Collision classes, types and import from tileMap are handled in main.lua, function resetGame().

I have already tried changing bullet's collider to other shapes, sizes, prevent it from rotating or putting two colliders for 'double-check' but nothing solved the issue – collision is simply not being registered.

Thank you for so much help and support, let me know if I can improve the way of sharing information and details with all of you guys!
Attachments
project.zip
(47.45 MiB) Downloaded 229 times
User avatar
marclurr
Party member
Posts: 145
Joined: Fri Apr 22, 2022 9:25 am

Re: Collision not being registered

Post by marclurr »

I think the problem is the call to setPosition. This seems to cause some weirdness in the Box2D internals. I'm not sure exactly why it sometimes works and other times doesn't but I suspect it's not meant to be used to move things into a colliding state. I remove the setPosition and setAngle calls from the bullet update method and added the below to the constructor and everything works perfectly:

Code: Select all

local vx = math.cos(angle) * self.speed
local vy = math.sin(angle) * self.speed
self.collider:setLinearVelocity(vx, vy)
self.collider:setAngle(self.angle)
DannyMcWopper
Prole
Posts: 8
Joined: Tue Nov 14, 2023 4:01 pm

Re: Collision not being registered

Post by DannyMcWopper »

marclurr wrote: Fri Dec 01, 2023 2:46 pm I think the problem is the call to setPosition. This seems to cause some weirdness in the Box2D internals. I'm not sure exactly why it sometimes works and other times doesn't but I suspect it's not meant to be used to move things into a colliding state. I remove the setPosition and setAngle calls from the bullet update method and added the below to the constructor and everything works perfectly:

Code: Select all

local vx = math.cos(angle) * self.speed
local vy = math.sin(angle) * self.speed
self.collider:setLinearVelocity(vx, vy)
self.collider:setAngle(self.angle)
I was thinking about this part of code but for some reason decided that this could not be it)

Indeed this approach fixed the issue, thank you so much! However I have put this code into bullet.update (changing angle to self.angle). Otherwise trajectory is affected by enemies the bullet has already hit. Enemy collider velocity is set to 0,0 after being hit by bullet to prevent any movement.

Anyway, to sum up, moving colliders by collider:setPosition() seems like a poor practice and should not be used unless truly intended, right?

Also, it feels like too much, but how do I check the Box2D internals to monitor things like this issue?

Thank you so much once again!
User avatar
marclurr
Party member
Posts: 145
Joined: Fri Apr 22, 2022 9:25 am

Re: Collision not being registered

Post by marclurr »

DannyMcWopper wrote: Fri Dec 01, 2023 4:27 pm Anyway, to sum up, moving colliders by collider:setPosition() seems like a poor practice and should not be used unless truly intended, right?

Also, it feels like too much, but how do I check the Box2D internals to monitor things like this issue?
From what I can tell, you're right. Best to let the physics engine perform all movement, but I'm no expert on Box2D. I had a read around and play with Windfield because your problem interested me. There may be a way to use setPosition in a reliable way but I didn't find anything in my cursory search.

On a side note, and while I wouldn't recommend changing your approach this far down the line, I personally wouldn't bother using a full-blown physics engine for a game like this, you end up banging your head against the wall over really fiddly problems. In the past I've used Bump which only handles AABB (rectangles) colliders, with a few choices for how to resolve collisions (i.e., slide along surface, stop at contact point, bounce or do nothing). it's also not too difficult to implement a really simple grid aligned tilemap collision system, coupled with a basic AABB overlap routine and you can implement pretty much any 2D game. That's not to take anything away from what you've done, it's not bad at all especially if this is your first project.
Post Reply

Who is online

Users browsing this forum: No registered users and 2 guests