This is the code I'm using to detect collisions for projectiles in my game:
From the windfield physics library, I'm using its collider:enter('name') function which is basically just the beginContact callback for box2D, and it works perfectly fine until multiple bullets try to hit an enemy at the same time. In that case, one of them hits the enemy, and the others simply phases through without colliding. I have this collision function inside the main enemy loop because it allows me to create AoE projectiles that collide with multiple enemies for explosions etc. Is there anyway to still use box2D physics for my projectiles while also fixing the phasing issue?
Here's the function in the enemy main loop:
I'd really appreciate any ideas, I've been lost for a while now, thanks
enemy/projectile collisions stops working when colliding with 2 or more bullets
Forum rules
Before you make a thread asking for help, read this.
Before you make a thread asking for help, read this.
-
- Prole
- Posts: 2
- Joined: Fri Dec 13, 2024 4:50 am
Re: enemy/projectile collisions stops working when colliding with 2 or more bullets
I don't have experience with Windfield, but if I'm reading the source right, the library only keeps the enter data for the 'latest' contact with a certain collision class.tektonic99 wrote: ↑Fri Dec 13, 2024 5:52 am In that case, one of them hits the enemy, and the others simply phases through without colliding.
It's this line here: https://github.com/a327ex/windfield/blo ... t.lua#L816
(edit: which means, only the latest bullet to hit on that frame would occupy the enter data, not all of them)
However, one line above that, it does keep track of multiple contacts in the stay data.
So instead of...
Code: Select all
local collision_data = ec:getEnterCollisionData('projectile')
local b = collision_data.collider:getObject()
(...)
Code: Select all
local all_stay_data = ec:getStayCollisionData('projectile')
for stay_index = 1, #all_stay_data do
local collision_data = all_stay_data[stay_index]
local b = collision_data.collider:getObject()
(...)
end
Re: enemy/projectile collisions stops working when colliding with 2 or more bullets
...By the way, I think callbacks would be the ideal pattern for this projectile hit behavior, where you only need to react once when the event happens.
So you could be processing the projectile hits inside the callback you give to Windfield's :setPreSolve(), instead of polling every frame with enemyCollisionManager().
Docs link:
https://github.com/a327ex/windfield?tab ... vecallback
So you could be processing the projectile hits inside the callback you give to Windfield's :setPreSolve(), instead of polling every frame with enemyCollisionManager().
Docs link:
https://github.com/a327ex/windfield?tab ... vecallback
-
- Prole
- Posts: 2
- Joined: Fri Dec 13, 2024 4:50 am
Re: enemy/projectile collisions stops working when colliding with 2 or more bullets
Thanks for the feedback. I moved everything to :setPreSolve() and changed the AoEs to queries instead of physical bodies. It's all working as expected now. Thanks a lot for the idea and explanation, helped me a lot.RNavega wrote: ↑Sat Dec 14, 2024 8:33 am ...By the way, I think callbacks would be the ideal pattern for this projectile hit behavior, where you only need to react once when the event happens.
So you could be processing the projectile hits inside the callback you give to Windfield's :setPreSolve(), instead of polling every frame with enemyCollisionManager().
Docs link:
https://github.com/a327ex/windfield?tab ... vecallback
Re: enemy/projectile collisions stops working when colliding with 2 or more bullets
also, careful with organized tables loops (such as ipairs), because when you remove an item while iterating (like on your line 25), the next iteration index has been moved 1 down. I would recommend iterating end-to-start
https://stackoverflow.com/questions/123 ... -iterating
https://stackoverflow.com/questions/123 ... -iterating
poof()
Re: enemy/projectile collisions stops working when colliding with 2 or more bullets
After reading your comment (great catch btw), I was going to suggest "use a linked list, it's very fast for removing arbitrary items".
But after profiling a bit, there's some considerations re: performance:
Code: Select all
Starting...
0.0088169407157843 <-- LINKED-LIST (REMOVE MIDDLE)
0.015085826617797 <-- TABLE (REMOVE MIDDLE)
0.0037212258294996 <-- TABLE (REMOVE LAST)
0.017078480483006 <-- LINKED-LIST (ITERATE)
0.0058362494843696 <-- TABLE (ITERATE)
Finished.
- Removing the last item is by far the fastest with table.remove() than with a linked list (like when you don't use an index, it pops the last item).
- Iterating the entire sequence is faster when done with an index FOR loop and the array part of a table, than using a WHILE loop and iterating all nodes in the linked list.
The code I used for those timings is this, the double-linked list implementation is from me:
Code: Select all
io.stdout:setvbuf('no')
-- Used for making DOUBLY-LINKED LISTS of nodes.
--
-- The list itself is a table that stores a reference to the first
-- and last nodes, if the list isn't empty:
-- {[1] = FIRST NODE, [2] = LAST NODE}
--
-- For maximum speed, the nodes are array-only tables in this format:
-- {[1] = VALUE, [2] = PREVIOUS NODE, [3] = NEXT NODE}
local LinkedList = {}
function LinkedList.new(self, ...)
local newList = {
nil, -- [1] = Reference to the first node, if any.
nil, -- [2] = Reference to the last node, if any.
-- Functions.
add = LinkedList.add,
disconnect = LinkedList.disconnect,
clear = LinkedList.clear,
}
return newList
end
setmetatable(LinkedList, {__call = LinkedList.new})
function LinkedList.add(list, value)
local newNode = {value, list[2], nil}
if list[2] then
list[2][3] = newNode
else
-- If there's no last node then there's no first node either, because
-- a one-node list has the same node in both first and last places.
list[1] = newNode
end
-- Set the new node as the last.
list[2] = newNode
return newNode
end
function LinkedList.disconnect(list, node)
-- Reconnect the neighboring nodes, if any, so as to remove the
-- input node from the list.
--
if node[2] then -- If the node has another behind it (so this isn't the first node).
node[2][3] = node[3]
if node[3] then -- If the node has another ahead of it (so this isn't the last node).
node[3][2] = node[2]
else
list[2] = node[2]
end
elseif node[3] then -- If this (first node) has another in front of it.
node[3][2] = nil
list[1] = node[3]
else -- Otherwise this was both the first and last node, the list is now empty.
list[1] = nil
list[2] = nil
end
-- For garbage-collection safety, remove references to other nodes.
node[2] = nil
node[3] = nil
end
function LinkedList.clear(list)
local node = list[1]
local oldNode
while node do
oldNode = node
node = node[3]
-- Remove references to other nodes.
oldNode[2] = nil
oldNode[3] = nil
end
list[1] = nil
list[2] = nil
end
-- =====================
-- MEASURE TIMINGS
-- =====================
function deltaTime(func)
local shortestTime = math.huge
for x = 1, 3 do
local startTime = love.timer.getTime()
for y = 1, 100000 do
func()
end
local endTime = love.timer.getTime() - startTime
if endTime < shortestTime then
shortestTime = endTime
end
end
return shortestTime
end
local list = LinkedList()
local middleNode
for x = 1, 26 do
local node = list:add(x)
if x == 13 then
middleNode = node
end
end
local listDisconnect = list.disconnect
local listAdd = list.add
function workListMiddle()
local middleValue = middleNode[1]
listDisconnect(list, middleNode)
middleNode = listAdd(list, middleValue)
end
function workListIterate()
local result = 0
local node = list[1]
while node do
result = result + node[1]
node = node[3]
end
return result
end
local array = {}
for x = 1, 26 do
array[x] = x
end
local tableRemove = table.remove
local tableInsert = table.insert
function workTableMiddle()
local lastValue = array[13]
tableRemove(array, 13)
tableInsert(array, 13, lastValue)
end
function workTableIterate()
local result = 0
for x = 1, 26 do
result = result + array[x]
end
return result
end
function workTableLast()
local lastValue = array[26]
tableRemove(array)
tableInsert(array, lastValue)
end
print('Starting...')
love.timer.sleep(2)
print(deltaTime(workListMiddle), '<-- LINKED-LIST (REMOVE MIDDLE)')
love.timer.sleep(1)
print(deltaTime(workTableMiddle), '<-- TABLE (REMOVE MIDDLE)')
love.timer.sleep(1)
print(deltaTime(workTableLast), '<-- TABLE (REMOVE LAST)')
print()
love.timer.sleep(1)
print(deltaTime(workListIterate), '<-- LINKED-LIST (ITERATE)')
love.timer.sleep(1)
print(deltaTime(workTableIterate), '<-- TABLE (ITERATE)')
print('Finished.')
function love.keypressed(key)
if key == 'escape' then
love.event.quit()
end
end
But this is stuff that probably won't make a perceptible difference, you're not gonna have tens of thousands of projectiles on screen.
Who is online
Users browsing this forum: Ahrefs [Bot], Amazon [Bot], Bing [Bot] and 6 guests