Loop Through Bodies and Only Destroy One Body

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
User avatar
Sammm
Prole
Posts: 37
Joined: Fri Sep 09, 2022 4:39 pm

Loop Through Bodies and Only Destroy One Body

Post 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 64 times
(W, A, D to move, S to smash an enemy)
Thanks for any help!!
Lua is LÖVE, lua is life
User avatar
ReFreezed
Party member
Posts: 612
Joined: Sun Oct 25, 2015 11:32 pm
Location: Sweden
Contact:

Re: Loop Through Bodies and Only Destroy One Body

Post 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.
Tools: Hot Particles, LuaPreprocess, InputField, (more) Games: Momento Temporis
"If each mistake being made is a new one, then progress is being made."
User avatar
Sammm
Prole
Posts: 37
Joined: Fri Sep 09, 2022 4:39 pm

Re: Loop Through Bodies and Only Destroy One Body

Post 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?
Lua is LÖVE, lua is life
User avatar
ReFreezed
Party member
Posts: 612
Joined: Sun Oct 25, 2015 11:32 pm
Location: Sweden
Contact:

Re: Loop Through Bodies and Only Destroy One Body

Post 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'.
Tools: Hot Particles, LuaPreprocess, InputField, (more) Games: Momento Temporis
"If each mistake being made is a new one, then progress is being made."
User avatar
ddabrahim
Party member
Posts: 182
Joined: Mon May 17, 2021 8:05 pm
Contact:

Re: Loop Through Bodies and Only Destroy One Body

Post 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.
User avatar
ReFreezed
Party member
Posts: 612
Joined: Sun Oct 25, 2015 11:32 pm
Location: Sweden
Contact:

Re: Loop Through Bodies and Only Destroy One Body

Post 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.
Tools: Hot Particles, LuaPreprocess, InputField, (more) Games: Momento Temporis
"If each mistake being made is a new one, then progress is being made."
User avatar
Sammm
Prole
Posts: 37
Joined: Fri Sep 09, 2022 4:39 pm

Re: Loop Through Bodies and Only Destroy One Body

Post by Sammm »

ReFreezed, checking the enemy fixture worked! Thanks for the help!
Lua is LÖVE, lua is life
User avatar
ddabrahim
Party member
Posts: 182
Joined: Mon May 17, 2021 8:05 pm
Contact:

Re: Loop Through Bodies and Only Destroy One Body

Post 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.
User avatar
ReFreezed
Party member
Posts: 612
Joined: Sun Oct 25, 2015 11:32 pm
Location: Sweden
Contact:

Re: Loop Through Bodies and Only Destroy One Body

Post 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.)
Tools: Hot Particles, LuaPreprocess, InputField, (more) Games: Momento Temporis
"If each mistake being made is a new one, then progress is being made."
User avatar
ddabrahim
Party member
Posts: 182
Joined: Mon May 17, 2021 8:05 pm
Contact:

Re: Loop Through Bodies and Only Destroy One Body

Post by ddabrahim »

Thank you for the explanation, really appreciate it!
Post Reply

Who is online

Users browsing this forum: No registered users and 16 guests