Multiple parts hitting each other at the same time

Sorry for the butchered paragraph below, I just copy pasted it from a server:

I’m trying to make a system where two blocks collide, they delete themselves, and then they spawn a new one (so like cube combinations or a merge game).

However, a problem arises when three or more blocks collide at the same time. When this happens, it deletes all of the touching parts, and then makes n - 1 new parts.

The formula for this is n = touching parts, and new parts = n - 1 (it always makes as many new parts as 1 less than the amount of touching parts at the start)

For example let’s say 4 blue cubes touch each other at the same time. These four blue cubes will then be deleted by my script, and then three yellow cubes will spawn.
Edit: Which is what I do not want to happen. Two blue cubes = One yellow cube so it should only spawn two, not three!

I have added debounce into my script, I have even changed part.CanTouch on and off. I have even added values into all cubes and then disabled values in other parts when they touch. I even have added random wait values but to no avail; nothing I have tried works.

If a block hits two other parts on the same frame they all get turned into a new one and I cant seem to stop it. Is there any solution to this problem?

I currently have a script that semi-works. But I want a script that works all the time. Here is a video (gyazo, I don’t know how else to put a video here)

https://gyazo.com/7059c10066f8c58b775dc62bf6dbc2c5

1 Like

Please share the script you worked on. We need your progress to help you.

Whoops lol! Here it is:

script.Parent.Touched:Connect(function(hit)

	print(hit)

	if script.Parent.CanTouch == true then 
		if hit.Name == CurrentElement then
			script.Parent.CanTouch = false

			-- Random Wait
			local randomnum = math.random(15,100)
			local waitnum = randomnum/1000
			task.wait(waitnum)

			local randomnum = math.random(15,100)
			local waitnum = randomnum/1000
			task.wait(waitnum)

			-- Check if it has been disabled by the other hit part yet
			if Enabled.Value == true then
				-- If hasn't been disabled then check stuff then disable the other part as fast as possible

				--if hit.Name == CurrentElement then

				if hit:FindFirstChild("Enabled") then
					hit:FindFirstChild("Enabled").Value = false
				end

				-- Summon next element
				if script.Parent.CanTouch == false then
					if hit and script.Parent then
						SpawnElement(script.Parent.Position + Vector3.new(0, ydisplacement, 0), CombinedElement)
					end
				end
				
				-- Delete script.Parent and hit
				hit:Destroy()
				script.Parent:Destroy()
				task.wait(99999999)
				--end

			end

		end

		task.wait(0.15)

	end

end)

Edit: I think I forgot to mention but this script is cloned into every cube.

Message deleted…

It did not work

Hi there

Quick Solution:
One solution you could try would be to replace the ‘Enabled’ state with a ‘Priority’ state.
So you would check which part has priority. With that, you could also remove the random wait.

if hit.Name == CurrentElement then
    script.Parent.CanTouch = false

	-- Remove
	local randomnum = math.random(15,100)
	local waitnum = randomnum/1000
	task.wait(waitnum)
	local randomnum = math.random(15,100)
	local waitnum = randomnum/1000
	task.wait(waitnum)

	-- Check priority
	if MyPriority > hitPartPriority then

Improvement Suggestion
Also I would suggest some other improvements you could make.
Since you put a script into every single part, it would be better to have only one or two handle all the parts.

To achieve this, you can implement a module.
You can read the documentation for this:

Also something that could help to better understand modules:

Another thing is to use attributes and tags, which is a better way than StringValues etc. in your parts.
Here’s the documentation for this:

Implementation
Of course I won’t leave you hanging with just the documentation, so I tried to implement it myself. You might need to make some changes to certain parts though, because I don’t know all of your code.

First you need to create the module. Put it somewhere into ServerScriptService:

-- Create a bindable event called 'SpawnElement' for this (You can change the name if you want)
local spawnElementEvent = game.ReplicatedStorage.SpawnElement -- Path to your bindableEvent

local Element = {}
Element.__index = Element

function Element.new(block : Part,element,currentElement,combinedElement,priority)
	local newElement = {}
	setmetatable(newElement,Element)
	
	-- Add attributes to the block
	block:SetAttribute("Element",element)
	block:SetAttribute("CurrentElement",currentElement)
	block:SetAttribute("Priority",priority)
	
	-- Note: Naming with element and currentElement is a bit confusing, so maybe try something else
	
	-- Initialize values
	newElement.Block = block
	newElement.Element = element
	newElement.CurrentElement = currentElement
	newElement.CombinedElement = combinedElement
	newElement.Priority = priority
	
	return newElement
end

function Element:InitializeTouchedBehaviour()
	local currentElement = self.CurrentElement
	local touched = false

	self.Block.Touched:Connect(function(hit)
		-- Get the element of the touched block
		local element = hit:GetAttribute("Element")
		local priority = hit:GetAttribute("Priority")

		-- Check if the touched block has an element attribute
		-- Check if the touched block's element is equals to currentElement
		if element and currentElement == element and not touched then
			-- Debounce
			touched = true

			-- Check which block has priority
			if self.Priority > priority then
				-- Notify your server script to spawn a new element
				spawnElementEvent:Fire(self)
				
				-- Cleanup
				hit:Destroy()
				self.Block:Destroy()
				setmetatable(self,nil)
			end
		end
	end)
end

return Element

After that, create a server script, which is also somewhere inside of ServerScriptService:

local collectionService = game:GetService("CollectionService")
local spawnElementEvent = game.ReplicatedStorage.SpawnElement -- Path to your bindableEvent
local Element = require(script.Parent.ElementModule) -- Replace with the path to your module
local Y_DISPLACEMENT = 1

-- Loop through all the elements in the game tagged with 'Element' (You can change the name if you want)
for i,block in pairs(collectionService:GetTagged("Element")) do
	-- Get the attributes of the block
	local element = block:GetAttribute("Element")
	local currentElement = block:GetAttribute("CurrentElement")
	local priority = block:GetAttribute("Priority")
	
	-- Create a new element
	local newElement = Element.new(block,element,currentElement,"Steam",priority)
	newElement:InitializeTouchedBehaviour()
end

-- Note: I don't really know how your code for SpawnElement looks like, so you might need to do some changes here
spawnElementEvent.Event:Connect(function(element : Element)
	-- Create a new part (Customize as you please)
	local block = Instance.new("Part")
	block.Size = Vector3.new(1,1,1)
	block.Position = element.Block.Position + Vector3.new(0, Y_DISPLACEMENT, 0)
	block.Parent = workspace

	-- Create a new element
	local newElement = Element.new(block,element.CombinedElement,"Water","Steam",3)
	newElement:InitializeTouchedBehaviour()
end)

Also note that the parts you don’t create via script should have the ‘Element’ tag and all the attributes.

I hope this helps! I wasn’t really 100% sure if I got your idea right, so feel free to change some things or just ignore what I tried :wink:

1 Like

You have the touch event defined for each individual “ball”. The .Touched event ALWAYS fire in a specific order, the problem is each ball isn’t aware of the order.

Solution 1: Find something that updates instantly before the next .Touch event is executed. I think you’ve tried disabling .CanTouch and creating a BoolValue to check and both didn’t work. Try changing the name or setting an attribute instead.

Solution 2: Set up a global observer script. Have all other scripts talk to it with BindableFunction and get permission to confirm the touch instead.

Object 1, 2, and 3 all touch at the same time.
Object 1 requests for Object 2, global observer confirms
Observer reserves 1 and 2
2 requests for 1, request rejected since both are reserved
1 requests for 3, request rejected since 1 is reserved
3 requests for 1, request rejected since 1 is reserved
2 requests for 3, request rejected since 2 is reserved
3 requests for 2, request rejected since 2 is reserved

local debounceList = {} -- if object data is logged inside of this, it can't be touched
BindableFunction.OnInvoke = function(object, hit)
	-- object wants to trigger touch with hit, is this allowed?
	if debounceList[object] or debounceList[hit] then
		return false
	end
	-- both objects aren't reserved, we reserve them now and allow them to touch
	debounceList[object] = true
	debounceList[hit] = true
	task.delay(1, function() -- we unreserve them after 1 second to free up memory (by then hopefully the part is destroyed)
		debounceList[object] = nil
		debounceList[hit] = nil
	end)

	return true -- yes, the parts can touch
end
1 Like

The problem with a priority state is that I still don’t know which element block gets higher priority. With multiple blocks in the game, how do I ensure that they are all different priority states? I also don’t think this works for multiple collisions at the same time.

Maybe try using :GetPartsInPart as you can get an array of parts that are touching the part, that may not be the fix but I just thought using .Touched is kinda weird when you can just get a region which would work seamlessly too compared to a .Touched event. (.Touched might be bad because of lag on the server, im being a bit nitpicky)

The problem with solution 1 is changing the name or adding attributes is the same as turning off .CanTouch or a BoolValue with random wait times to check. I also don’t see how Solution 2 helps. Solution 2 is still recording every collision, no? So it will still get repeat collisions, which is the problem. Plus this seems like a lot of code just to check it with the server when the elements can check themselves?

(I may swap this over to a one-script handler in future to save lag, but for now I’m just trying to get the basics down. Basically, the same script gets cloned into every element block. With one script however, it still records ALL .Touched events so it doesn’t really change anything. I still need to figure out a way to solve this.)

How would you use :GetPartsInPart? They are only meant to touch and then delete each other and spawn a new block. One is not inside the other? :GetPartsInPart would have to utilize me making a new part of size 2,2,2, putting the same position as the touching part (which would still fire multiple times), and using it like that. Seems a lot of work and a lot more laggy than a simple .Touched, no?

Yeah the name seems weird but :GetPartsInPart() checks parts that collide with it too. Try it out!

Is that 100% reliable? If so, how would this work still? I’d have to loop through all elements loaded in the game and use :GetPartsInPart with them constantly? Surely this is more laggy? It would also still fire multiple times because each part would send what they are touching over and over, which doesn’t fix this.

You could make a table with all the element names and their priorities. Once a new element gets spawned you can set the priority of it. Also if a element is combining it creates a new element which has a new touched event. So if you have multiple parts they should still register.

So in here:

-- Create a new table
local elementTable = {
    ["Fire"] = 1,
    ["Water"] = 2,
}

spawnElementEvent.Event:Connect(function(element : Element)
	local block = Instance.new("Part")
	block.Size = Vector3.new(1,1,1)
	block.Position = element.Block.Position + Vector3.new(0, Y_DISPLACEMENT, 0)
	block.Parent = workspace

    -- Change this...
    local newElement = Element.new(block,element.CombinedElement,"Water","Steam",3)

    -- ...to this
    local newElement = Element.new(block,element.CombinedElement,"Water","Steam",elementTable[element.CombinedElement])
    -- Also you can change "Water" and "Steam" to whatever you want / need
    -- I just don't know how you get it in your script

	newElement:InitializeTouchedBehaviour()
end)

I don’t even have water and steam in my game… Why have two people mentioned it lol?

Anyway, this doesn’t seem to work. New elements only get spawned when two of the same parts touch each other, delete themselves, and spawn the next element (like a merge game). Having each part set as the same priority would do no good.

Water and Steam were just an example, since I don’t know how your SpawnElement function looks like and what elements you have.

Since you mentioned that you want to merge it if both parts are the same, you can remove currentElement part and just check if the current part has the same element as the other one.

About the priority, similar to how you did it the first time with the random wait time, you could make it so that you just give them a random priority instead of time. It’s basically the same but you don’t have a delay.

Okay, but this doesn’t fix multiple parts hitting each other at the same time? Also, your wording in the second paragraph doesn’t make sense to me. The random priority would definitely work, but it still doesn’t fix the collision problem.

You still get repeat collisions but the objects aren’t allowed to merge from the repeated collisions, which basically renders the repeat collisions useless. You’ve already tried to disable the .Touch event itself which doesn’t work. This is the alt solution to solve the problem.

The elements can’t check themselves, your original solution did that and it didn’t work.

Here is a video of the 2nd solution doing exactly what your test case needs it to do.

Demo File

GlobalCollisionValidation.rbxl (60.1 KB)

1 Like

Do you mind showing what it does when it is one cube that falls onto all four at once? Like this:
image

Demo file is here.

2 Likes

Sorry, just to clarify, can you tell me what you mean about the multiple collisions?
For me it seems to work fine.
One part can only collide with one other part, which then disables the collision event.

About the second paragraph:

function Element.new(block : Part,element,currentElement,combinedElement,priority)
	local newElement = {}
	setmetatable(newElement,Element)
	
	block:SetAttribute("Element",element)
	block:SetAttribute("CurrentElement",currentElement) -- Remove this
	block:SetAttribute("Priority",priority)
-- Change this...
if element and currentElement == element and not touched then

-- ...to this
if element and self.Element == element and not touched then