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)
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.
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
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
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?
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.
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