The Roblox engine’s discrepancy between server-client vision causes your issue. What the server sees is not always the same as what each player sees, and vice-versa.
Assuming you’re using a server script to control the particles, your wait() is based on server time (not client time). This is why the particles disappear prior to the completion of animation on the client.
You have two options:
- Move the particle effects to the client (preferred and efficient).
- Set up an event on the server, instead of using the wait() function.
Since option one is more performant in my opinion I’ll only cover that. However, if you find that option one does not meet your needs, I will gladly elaborate on setting up server-only events for particles.
Client-Based Particles:
Creating particles on the client is a very similar process to how one would create particles on the server. The fundamental difference is the use of Remote Events used to communicate information about particles from one client to every other client by using the server as a messenger.
In simple terms, we are going to trigger a remote event from the person throwing the bomb using:
--// CLIENT // --
local REP = game:GetService("ReplicatedStorage")
local event = REP:WaitForChild("RemoteEvent")
--// Run This Function On Explode //--
local function RelayExplosion(bomb)
RemoteEvent:FireServer(bomb)
end
After that, we will receive the data on the server using:
--// SERVER //--
local REP = game:GetService("ReplicatedStorage")
local BombEvent = REP:WaitForChild("RemoteEvent")
BombEvent.OnServerEvent:Connect(function(plr, bomb)
BombEvent:FireAllClients(bomb) -- This sends info about the bomb to every client
end)
Notice that inside of the function we have :FireAllClients(), this is what we use to relay the data.
Now, let’s go back to the client!
--// CLIENT AGAIN //--
local REP = game:GetService("ReplicatedStorage")
local BombEvent = REP:WaitForChild("BombEvent")
local function RelayExplosive(bomb)
BombEvent:FireServer(bomb)
end
RelayExplosive(bomb)
--// ALL Clients will receive this! //--
BombEvent.OnClientEvent:Connect(function(bomb)
local handle = bomb.Parent.Handle
-- Enable all your emitters here
wait(0.3)
-- Disable your emitters here
wait(8)
bomb:Destroy()
end)
Destroying the Server Bomb:
The only issue now is deciding when to remove the server bomb you created when it was thrown.
You could move that system over to a server relay as shown above, but that could cause issues with hit detection if you’re unsure where the bomb landed.
The simple way out would be to hand network ownership over to the player that threw the bomb. This would mean any actions done on that specific client would replicate across all clients.
--// SERVER //--
local REP = game:GetService("ReplicatedStorage")
local BombEvent = REP:WaitForChild("RemoteEvent")
BombEvent.OnServerEvent:Connect(function(plr, bomb)
bomb:SetNetworkOwner(plr) -- Sets network owner to thrower
BombEvent:FireAllClients(bomb) -- This sends info about the bomb to every client
end)
However, if you would like to delve deeper, you could count clients. To do this, we need to fire an event from each client after they’ve finished deleting the bomb and wait for each client to do so.
We just need to make a few changes to the scripts to apply this method:
--// CLIENT AGAIN x2 //--
local REP = game:GetService("ReplicatedStorage")
local BombEvent = REP:WaitForChild("BombEvent")
local function RelayExplosive(bomb)
BombEvent:FireServer(bomb, false) -- add false to not tally
end
RelayExplosive(bomb)
--// ALL Clients will receive this! //--
BombEvent.OnClientEvent:Connect(function(bomb)
local handle = bomb.Parent.Handle
-- Enable all your emitters here
wait(0.3)
-- Disable your emitters here
wait(8)
bomb:Destroy()
BombEvent:FireServer(bomb, true) -- this event counts towards a tally
end)
--// SERVER //--
local REP = game:GetService("ReplicatedStorage")
local BombEvent = REP:WaitForChild("RemoteEvent")
BombEvent.OnServerEvent:Connect(function(plr, bomb, tally)
if bomb and not tally then
bomb:SetAttribute("Witnesses", #game:GetService("Players"):GetPlayers()) -- Get # of clients
bomb:SetAttribute("Tally", 0) -- Set up tally for later
bomb:SetNetworkOwner(plr) -- Sets network owner to thrower
BombEvent:FireAllClients(bomb) -- This sends info about the bomb to every client
elseif bomb and tally then
bomb:SetAttribute("Tally", bomb:GetAttribute("Tally") + 1) -- Track # of clients finished
if bomb:GetAttribute("Tally") >= bomb:GetAttribute("Witnesses") then -- if maxClients == finishedClients:
bomb:Destroy()
end
end
end)
I hope this somewhat long-winded reply helps!