Lag is affecting how the localscript runs

  1. What do you want to achieve?
    So I work on this Friday Night Funkin’ game, which is basically a rhythm game where you have to hit notes, and the problem I am having is that if you are lagging or you end up lagging, the notes will delay and will appear off sync and clumped up, this only happens if you are lagging. I tried changing it using multiple different methods but I’m still unable to fix it. I thought that maybe it could be a problem with the FPS but I’m unsure and don’t know how to change it.

You can check the issue yourself here:
https://www.roblox.com/games/8153555440/FNR-Test-Place

  1. What is the issue?

As you can see, at first the notes appear properly as I’m not lagging, but as soon as I increase the graphics quality and start lagging the notes start to appear off sync and clumped up.

  1. What solutions have you tried so far? Did you look for solutions on the Developer Hub?

I tried getting help from a scripter and I looked through the dev forums multiple times but was unable to find anything that could help me with my issue.

This is the code I use to create notes through the client:

coroutine.wrap(function()
	repeat task.wait() until stage.Config.TimePast.Value > -4 / speed
	--repeat task.wait() until stage.MusicPart.Music.IsPlaying or stage.MusicPart.Vocals.IsPlaying
	
	local notekeys = {}
	local notes = basesong.notes or song.notes
	local sectionNum = 1
	local createNote = Instance.new("BindableEvent"); createNote.Parent = ui.Events

	local function makeNote(note, section, notenum)
		local timeposition     = note[1]
		local notetype         = note[2]
		local notelength    = note[3]
		local customnote = "normal"
		if note[4] then
			customnote = note[4]
		end 
		
		local timeframe = tomilseconds(( time / speed ) / player.Settings.ScrollSpeed.Value)
		local timepast = tomilseconds(stage.Config.TimePast.Value)
		--//local timepast = Util.tomilseconds(os.clock() - startTime) + offset
		local data = timeposition .. "~" .. notetype

		if timepast > timeposition - timeframe and not notekeys[data] then
			notekeys[data] = true --\\ confirm that nothing is duplicated

			local side = section.mustHitSection
			local actualnotetype, oppositeSide, hellNote = notetypeconvert(notetype)

			if not actualnotetype or not templates:FindFirstChild(actualnotetype) then return end --\\ debugging

			if oppositeSide then side = not side end
			side = side and "R" or "L"
			if not oppositeSide then ui.Side.Value = side end

			--\\ add note to game
			local slot = templates[actualnotetype]:Clone()
			
			if customnote == "normal" then
				slot.Frame.Type.Value = customnote
				slot.Frame.Arrow.Visible = true
			elseif customnote == nil then
				slot.Frame.Arrow.Visible = true
			elseif customnote == 1 then
				if stage.MusicPart.Vocals.SoundId == "rbxassetid://7520534314" then
					slot.Frame.Type.Value = "shoot"
					slot.Frame.ShootArrowLeft.Visible = true
				end
			elseif customnote == 2 then
				if stage.MusicPart.Vocals.SoundId == "rbxassetid://7910948478" then
					slot.Frame.Type.Value = "slime"
					slot.Frame.SlimeArrow.Visible = true
				elseif stage.MusicPart.Vocals.SoundId == "rbxassetid://7434809794" or stage.MusicPart.Vocals.SoundId == "rbxassetid://8090496191" or stage.MusicPart.Vocals.SoundId == "rbxassetid://7465895256" or stage.MusicPart.Vocals.SoundId == "rbxassetid://8102995543" or stage.MusicPart.Vocals.SoundId == "rbxassetid://7448220496" or stage.MusicPart.Vocals.SoundId == "rbxassetid://8329456961" then
					slot.Frame.Type.Value = "kr"
					slot.Frame.KRArrow.Visible = true
				elseif stage.MusicPart.Vocals.SoundId == "rbxassetid://7308983181" or stage.MusicPart.Vocals.SoundId == "rbxassetid://8153328371" or stage.MusicPart.Vocals.SoundId == "rbxassetid://8282329705" then
					slot.Frame.Type.Value = "static"
					slot.Frame.StaticArrow.Visible = true
				elseif stage.MusicPart.Vocals.SoundId == "rbxassetid://7520534314" then
					slot.Frame.Type.Value = "shoot"
					slot.Frame.ShootArrowRight.Visible = true
				end
			elseif customnote == 3 then
 				if stage.MusicPart.Vocals.SoundId == "rbxassetid://8085882227" or stage.MusicPart.Vocals.SoundId == "rbxassetid://7434809794" or stage.MusicPart.Vocals.SoundId == "rbxassetid://8090496191" or stage.MusicPart.Vocals.SoundId == "rbxassetid://7465895256" or stage.MusicPart.Vocals.SoundId == "rbxassetid://8102995543" or stage.MusicPart.Vocals.SoundId == "rbxassetid://7448220496" or stage.MusicPart.Vocals.SoundId == "rbxassetid://8329456961" then
					slot.Frame.Type.Value = "dust"
					slot.Frame.DustArrow.Visible = true
				elseif stage.MusicPart.Vocals.SoundId == "rbxassetid://8153328371" then
					slot.Frame.Type.Value = "ExePhantom"
					slot.Frame.ExePhantomArrow.Visible = true
				end
			elseif customnote == "sage" then
				slot.Frame.Type.Value = customnote
				slot.Frame.SageArrow.Visible = true
			elseif customnote == "ebola" then
				slot.Frame.Type.Value = customnote
				slot.Frame.EbolaArrow.Visible = true
			elseif customnote == 4 then
				
			else
				slot.Frame.Type.Value = customnote
				slot.Frame.Arrow.Visible = true
			end
			
			if keys == 4 then
				slot.Position = UDim2.new(1,0,6.666,0)
			else
				slot.Position = UDim2.new(1,0,7.666,0)
			end

			if tonumber(notelength) then
				slot.Frame.Bar.Size = UDim2.new(0.325, 0, notelength * (( 0.375 * speed ) * player.Settings.ScrollSpeed.Value ) / 100, 0)
			end

			slot:SetAttribute("Length", slot.Frame.Bar.Size.Y.Scale)
			slot:SetAttribute("Made", tick())
			slot:SetAttribute("NoteData", data)
			
			slot.Parent = ui.Game[side].Arrows.IncomingArrows
			
			tweenarrow(slot)

		elseif notekeys[data] then
			table.remove(notes[sectionNum].sectionNotes, notenum) --\\ removes notes that were already spawned
		end
	end
	
	createNote.Event:Connect(makeNote)

	local noteSpawn; noteSpawn = game["Run Service"].Heartbeat:Connect(function()

		local section = notes[sectionNum] or nil --// section used to check for notes

		--\\ Debugging
		if stage.Config.CleaningUp.Value or not section then noteSpawn:Disconnect() return end
		if not section.sectionNotes[1] then sectionNum += 1 return end

		--\\ Notes
		for notenum, note in pairs(section.sectionNotes) do
			createNote:Fire(note, section, notenum)
		end
		
	end)


end)()
1 Like

Is your computer a low-end one? That might have to do simply because your computer has difficulties running.

Another thing you could do is restrict yourself from using wait() loops, as they run every frames and could have an impact on your game lagging.

(By the way, continue the good work on UFS. :grin:)

1 Like

This isn’t a issue that only happens to me, it happens to anyone that lags in the game.

( Also, I no longer work on UFS, sorry. )

I took a quick look at your script, but I’ll ask incase I missed it;

What happens to the notes after they were pressed/missed, do you keep them somewhere or simply delete them?

The notes are Destroyed once they go off bounds or are pressed.

What does this function do? Tween the arrow upwards? If so, you may want to change where the arrow starts. Best way to do it is to send along time data and calculate how far up the board the arrow should start.

I had this problem myself when creating my own rhythm game.

The issue here is that you are using Heartbeat which runs every single frame. For example, there are two people who are playing the same song with the same charting.

The first person has an FPS of 60
The second person has an FPS of 30

The second person will be likely to have their notes off-sync because of how Heartbeat runs and RunService in general.

The second person’s Heartbeat will run 30 times a second.
The first person’s Heartbeat will run 60 times a second.

This means that the second person’s notes will be delayed because it is running less than the first person.

So, the Solution?
Ditch the Heartbeat function and just the whole RunService completely. The best option is to use os.clock()

It is possible to run 60 or 30 times a second no matter the frame rate.

local Last = 0
local Delay = 0.05
game:GetService'RunService'.Heartbeat:Connect(function()
	if os.clock() - Last >= Delay then
		print("Delta", os.clock() - Last)
		Last = os.clock()
	end
end)

This code will run every 0.05 seconds or any value you set for the variable Delay. In this case, Delay is 0.05. If you want better results, you can make it even smaller (don’t go too small or it will lag low-end devices)

Hi, sorry for the late reply but I tried using this method and it didn’t seem to fix the issue, I think the problem is mostly just related to the lag itself and not the fps, do you have any other ideas why that could be the case?