Stutter occurring on first coin touch and no subsequent ones

Hello,

So I have run into a rather strange issue that I cannot figure out. I have a coin that has a touch event on it which for now, simply plays a sound and makes the coin disappear. However when loading into the game, for some reason, the first coin that is touched causes a ~150ms lag spike. I have attached an image of the microprofiler. The culprit seems to be the two tasks labelled Worker::runJob and Write Marshalled. Like I said, it only happens on the first coin so I tried preloading the sound with contentprovider and other things like removing sounds and tween animations completely but it had no effect and the stutter still occurs. Does anyone have any ideas?

coinAnimator Script
local ReplicatedStorage = game:GetService('ReplicatedStorage')
local RunService = game:GetService('RunService')
local coinConfig = require(ReplicatedStorage:WaitForChild('coinConfig'))

local coin: Model = script.Parent

-- Create anchor part
local anchor = Instance.new("Part")
anchor.Name = "Anchor"
anchor.Size = Vector3.new(1, 1, 1)
anchor.Transparency = 1
anchor.CanCollide = false
anchor.Anchored = true
anchor.CFrame = coin.PrimaryPart.CFrame
anchor.Parent = coin

-- Weld parts to anchor part
for _, part in pairs(coin:GetDescendants()) do
	if part:IsA("BasePart") and part ~= anchor then
		part.Anchored = false
		local weld = Instance.new("WeldConstraint")
		weld.Part0 = anchor
		weld.Part1 = part
		weld.Parent = anchor
	end
end

coin.PrimaryPart = anchor

-- Movement settings
local startCFrame = anchor.CFrame
local startTime = tick()
local bobHeight = coinConfig.bobHeight     
local bobSpeed = coinConfig.bobSpeed   
local spinSpeed = coinConfig.spinSpeed  

RunService.Heartbeat:Connect(function()
	local elapsed = tick() - startTime
	local zOffset = math.sin(elapsed * bobSpeed) * bobHeight

	anchor.CFrame = startCFrame * CFrame.new(0, 0, zOffset) * CFrame.Angles(0, 0, elapsed * spinSpeed)
end)
coinController Script
local ContentProvider = game:GetService('ContentProvider')
local DebrisService = game:GetService('Debris')
local TweenService = game:GetService('TweenService')
local coin: Model = script.Parent
local hitBox: Part = coin.hitBox


local sound:Sound = coin:WaitForChild('coinSound')

local debounce = false

if not sound.IsLoaded then
	sound.Loaded:Wait()
end


hitBox.Touched:Connect(function(otherPart: BasePart)
	if debounce then return end

	local character = otherPart.Parent
	local humanoid: Humanoid = character:FindFirstChild('Humanoid')

	if humanoid then
		debounce = true
		hitBox.CanTouch = false

		sound.Parent = workspace
		sound:Play()
		sound.Ended:Connect(function()
			sound:Destroy()
		end)


		local tweens = {}
		for _, part in pairs(coin:GetDescendants()) do
			if part:IsA("BasePart") then
				local tween = TweenService:Create(part, TweenInfo.new(0.1), { Transparency = 1 })
				table.insert(tweens, tween)
				tween:Play()
			end
		end
		
		tweens[1].Completed:Wait()
		DebrisService:AddItem(coin, 0)
	end
end)

Image of Explorer

Thank you in advance for any help :slight_smile:

1 Like

i think this is a studio bug where in studio, the first time :Destroy() is run on anything, the game stutters. happens to me too consistently every time. it should be fine in regular Roblox

1 Like

Hey, thanks for your response. I actually found a fix. Rather than adding the scripts to each coin in the workspace, I put the scripts in serverscriptservice and now loop through the coins in the workspace and run the code for each coin. For some reason this fixes the frame latency issue. I will be honest, I have no idea why this works, I could understand if the issue was caused when having many coins but it happened even with just one so it wasn’t as if it was being caused by hundreds of scripts trying to run.

Another thought I had which led me to the serverscriptservice solution was that, in my mind at least, if the scripts were in the workspace, they had to communicate with the server to then replicate to other clients, whereas if they start in the server, they are sent to the client directly the first time and don’t have to first communicate with the server and then back to the client. This could be completely wrong and I think it may be as I am pretty sure any non local script is a server script whether in the workspace or not but again, I am hoping someone can educate me on this as I am interested to know the root cause of this issue

1 Like

I should not, my ‘solution’ does not work if you want the coins to only be collected per client. I will try your solution and see if the issue persists on the Roblox client

Leaving this here for any future people who come across this issue.

The best fix I have found for my use case which is to have coins that can be picked up by a player and ‘disappear’ locally but remain on the server side for other players to pick up - whilst eliminating the lag spike, is to use a LocalScript for the handling of deleting the coin for the local player, sounds and animation of the coin (code below)

LocalScript in StarterCharacterScripts
local Players = game:GetService('Players')
local DebrisService = game:GetService('Debris')
local TweenService = game:GetService('TweenService')
local ReplicatedStorage = game:GetService('ReplicatedStorage')
local coinCollected = ReplicatedStorage:WaitForChild('coinCollected')

coinCollected.OnClientEvent:Connect(function(coin)
	local sound: Sound = coin:FindFirstChild('coinSound')

	if sound then
		sound.Parent = workspace
		sound:Play()
		sound.Ended:Connect(function()
			DebrisService:AddItem(sound, 0)
		end)
	end

	local tweens = {}
	for _, part in pairs(coin:GetDescendants()) do
		if part:IsA("BasePart") then
			local tween = TweenService:Create(part, TweenInfo.new(0.1), { Transparency = 1 })
			table.insert(tweens, tween)
			tween:Play()
		end
	end
	tweens[1].Completed:Wait()
	DebrisService:AddItem(coin, 0)
end)

I then added a coin controller script to ServerScriptService which populates each coin with a .Touched event. When touched this event fires a remoteEvent so the coins deletion can then be handled in the script above. Code is below

coinController script in ServerScriptService
local ReplicatedStorage = game:GetService('ReplicatedStorage')
local coinCollected = ReplicatedStorage:WaitForChild('coinCollected')
local coinsFolder = workspace:WaitForChild('coins')
for _, coin in pairs(coinsFolder:GetChildren()) do
	local hitBox: Part = coin.hitBox
	local debounce = false

	hitBox.Touched:Connect(function(otherPart: BasePart)
		if debounce then return end
		local character = otherPart.Parent
		local player = game.Players:GetPlayerFromCharacter(character)
		local humanoid: Humanoid = character:FindFirstChild('Humanoid')
		if humanoid and player then
			debounce = true
			-- Fire to that specific player,
			coinCollected:FireClient(player, coin)
		end
	end)
end

I hope this helps anyone one in the future who experiences this lag spike issue and isn’t sure how to fix it

1 Like

I assume you used ai to generate this code but you shouldnt use debris unless you need to destroy something after a certain amount of time and making an unnecessary table for just 1 tween isn’t really good either.

If anything you shouldn’t make unnecessary posts to then post a bunch of crap. If you have found a solution then mark the post solved rather than posting misleading garbage ai code that you aren’t even 100% sure is good or correct. All development resources should also be posted in Latest Resources topics - Developer Forum | Roblox
instead of here.

You should be careful assuming, this was not AI generated. I used debris service as I saw a post somewhere online that said debris was the better more modern way of removing objects. It mentioned that using Destroy was seen as inferior, I think for memory reasons if I remember correctly. If you are saying that :Destroy() is actually the one I should be using in situations like this then that is good to know, thanks

EDIT:
I checked the API reference and you are correct regarding using Destroy. I had previously thought it left parts as orphans and thus potential memory issues but this is incorrect. I used debris as I was trying things to see if I could fix the lag spike

Rather than grandstanding, could you please let me know why you think this code is garbage so that myself and others can learn. If you have a better way of doing it, it would be helpful to share that knowledge rather than just calling someone else’s attempt garbage whilst providing no solution of your own.

You should probably avoid using debris as it’s pretty much a legacy service and use

task.delay(1, function()
    p:Destroy()
end)
local tweens = {}
	for _, part in pairs(coin:GetDescendants()) do
		if part:IsA("BasePart") then
			local tween = TweenService:Create(part, TweenInfo.new(0.1), { Transparency = 1 })
			table.insert(tweens, tween)
			tween:Play()
		end
	end
tweens[1].Completed:Wait()

this code here should be changed to this instead

for _, part in coin:QueryDescendants("BasePart") do
	local tween = TweenService:Create(part, TweenInfo.new(0.1), { Transparency = 1 })
	tween:Play()
end

theres absolutely no need for tables in here and querydescendants is much better in here plus you shouldn’t use pairs or ipairs as they are less performant and also pretty much useless nowadays.

if sound then
		sound.Parent = workspace
		sound:Play()
		sound.Ended:Connect(function()
			DebrisService:AddItem(sound, 0)
		end)
	end

this block of code seems pointless just do sound:Play() and upon deleting the coin the sound will also be deleted with it.

2 Likes

You shoud probably fact check stuff before actually believing it, especially if it’s an old post.

Yes this is valid. It’s quite hard to find sources of truth for Roblox dev I find. Obviously there is the main reference site and their tutorials. However outside of that, it’s pretty much the Wild West of teenagers giving their version of the truth

Thank you for this explanation.

Regarding the sound code block and moving it to the workspace. This was again just me trying to think about what could be causing the lag spike. Because it was happening on the first coin touch, I thought perhaps it was the sound being loaded into memory. So I thought if I perhaps loaded it into the workspace it might make a difference but you are right, it doesn’t actually really achieve anything because it still starts in the coin so there is nothing to be achieved by moving it to the workspace as soon as the coin is touched. I should have removed this before posting.

The only reason for the .Ended connect function was because I didn’t want the coin sound to cut off as soon as it was touched so needed to let the sound play and then delete only once it had finished. What is the best practice in the situation?

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.