Page 1 of 1

Loop Through Bodies and Only Destroy One Body

Posted: Wed Sep 28, 2022 4:47 pm
by Sammm
Hey everyone!

I'm trying to handle the collisions between the player and multiple enemies with collision callbacks. Here's how I add multiple enemies to the game:

Code: Select all

local allEnemies

function addEnemy(x,y)
	enemy = {}
	enemy.isHit = false
	enemy.body = love.physics.newBody(world, x, y, "dynamic")
	enemy.shape = love.physics.newRectangleShape(50, 100)
	enemy.fixture = love.physics.newFixture(enemy.body, enemy.shape)
	enemy.fixture:setUserData("Enemy")

	table.insert(allEnemies, enemy)
end

function love.load()	
	allEnemies = {}

	addEnemy(100, 0)
	addEnemy(200, 0)
	addEnemy(300, 0)
end
Then, I can draw the enemies like this:

Code: Select all

	for _, enemy in ipairs(allEnemies) do
		if enemy.body:isDestroyed() == false then
			love.graphics.polygon("fill", enemy.body:getWorldPoints(enemy.shape:getPoints()))
		end
	end
However, when I try to handle the collisions, when the player smashes down on the enemy, all of the enemies are destroyed, not just the single one he collided with. Why is that? Here's my code for that:

Code: Select all

function beginContact(a, b, coll)
	for _, enemy in ipairs(allEnemies) do
		if a:getUserData() == "Player" and b:getUserData() == "Enemy" and velY >= 1000 or a:getUserData() == "Enemy" and b:getUserData() == "Player" and velY >= 1000 then
			player.jumps = 2
			enemy.isHit = true
			enemy.body:destroy()
		end

		if a:getUserData() == "Player" and b:getUserData() == "Enemy" and velY <= 1000 or a:getUserData() == "Enemy" and b:getUserData() == "Player" and velY <= 1000 then
			player.isHit = true
		end
	end
end
Here's the love file if you need it:
MyGame.love
(2.05 KiB) Downloaded 66 times
(W, A, D to move, S to smash an enemy)
Thanks for any help!!

Re: Loop Through Bodies and Only Destroy One Body

Posted: Wed Sep 28, 2022 6:25 pm
by ReFreezed
When you loop through 'allEnemies' in beginContact() you never check if 'a' or 'b' is related to 'enemy' before you start doing stuff to it and the player.

Re: Loop Through Bodies and Only Destroy One Body

Posted: Wed Sep 28, 2022 6:59 pm
by Sammm
I think I see what you're saying, but I'm still unclear on how to fix it, sorry. Could you perhaps explain further?

Re: Loop Through Bodies and Only Destroy One Body

Posted: Wed Sep 28, 2022 9:05 pm
by ReFreezed
The 'a' and 'b' arguments are the fixtures involved in the collision, and you store the enemy fixtures in enemy.fixture, so simply replace the a:getUserData()=="Enemy" checks with a==enemy.fixture, and the same with 'b'.

Re: Loop Through Bodies and Only Destroy One Body

Posted: Wed Sep 28, 2022 9:30 pm
by ddabrahim
The physics engine has built-in collision detection and you did not actually checked if enemy is touching the player, so I was able to solve this problem by doing just that:

Code: Select all

if enemy.body:isDestroyed() == false and enemy.body:isTouching(player.body) then
	player.jumps = 2
	enemy.isHit = true
	enemy.body:destroy()
end
But it is certainly worth to investigate what ReFreezed was suggesting. I have no clue what a fixture is, I have never used the physics engine before :P

Hope it helps anyway.

Re: Loop Through Bodies and Only Destroy One Body

Posted: Wed Sep 28, 2022 9:57 pm
by ReFreezed
ddabrahim wrote: Wed Sep 28, 2022 9:30 pm The physics engine has built-in collision detection and you did not actually checked if enemy is touching the player (...)
beginContact() is a callback called by the physics engine when there is a collision. There's no need to verify the collision.

Re: Loop Through Bodies and Only Destroy One Body

Posted: Wed Sep 28, 2022 10:43 pm
by Sammm
ReFreezed, checking the enemy fixture worked! Thanks for the help!

Re: Loop Through Bodies and Only Destroy One Body

Posted: Thu Sep 29, 2022 6:29 am
by ddabrahim
ReFreezed wrote:beginContact() is a callback called by the physics engine
Oh I didn't see this in the docs. In that case the For loop within the callback is not overkill? Can't you just somehow get the parents of the fixtures and their bodies? Running the for loop every single time 2 fixtures overlap sounds heavy to me.

If using a for loop is a must, what is the benefit of using beginContact() callback instead of just running the for loop within love.update and check if enemy.body:isTouching(player.body)?

Sorry for spamming this topic, I am just curious. For first glance I dislike the way this callback works, just trying to understand if it really beneficial to implement it the way Sammm did it :awesome:

Thank you.

Re: Loop Through Bodies and Only Destroy One Body

Posted: Thu Sep 29, 2022 4:01 pm
by ReFreezed
The related function is World:setCallbacks. The 'enemy' table is not related to Box2D and so isn't part of the internal hierarchy of objects (not that there is much of a hierarchy), but instead of doing enemy.fixture:setUserData("Enemy") you could do enemy.fixture:setUserData(enemy) and then use fixture:getUserData() to get the 'enemy' table directly in the callback. That way no loops are required.

The reason beginContact (and other callbacks) exist is to notify you of events so you don't have to implement the functionality yourself. You don't have to use the callbacks, or Box2D at all - you can implement all physics and stuff you want without them. In fact, why use Lua and LÖVE when you can just use C/C++ directly? Why not just use assembly for everything? Why not just write machine code directly? (Extrapolating is fun.)

Re: Loop Through Bodies and Only Destroy One Body

Posted: Thu Sep 29, 2022 7:19 pm
by ddabrahim
Thank you for the explanation, really appreciate it!