Firerate not consistent across different framerates

Hello! I’ve been having problems with the fire-rate for my FPS game. When it is on 60 FPS it fires slower than it should, and at 240 FPS it fires exactly how fast it should. I need it to just match the right fire-rate, which in this case is 700.

robloxapp-20250202-2012338.wmv (1.5 MB)

My playback seems to be going faster for the 60 FPS test, but it is slower in real time. Here is a link to the game if you wanted to try it in real-time: Bullet Blox - Roblox

Keep in mind that the recoil is different because the line of code that gives it a kick is getting called less often. At least that is what I hope is going on, pretty confident though.

I’ve tried a few different solutions, such as firing the Activate_Weapon() in a for loop rather than when the is_Shooting variable is set to true in the RenderStepped function. Another one I tried was to fire the Activate_Weapon() function via remote function from the server when the tool is activated. It ended up doing the exact same thing. I hope this made sense.

My current code:

-- Viewmodel Handler

function Activate_Weapon()
	if inventory.viewmodel and not Shoot_Debounce then
		Shoot_Debounce = true
		
		RecoilSpring:AddVelocity(inventory.data.VM_Settings.Recoil_Amount)
		
		task.spawn(function()
			local result = Remotes.Functions.ActivateWeapon:InvokeServer(inventory.viewmodel.AimPart.CFrame.Position, inventory.viewmodel.FrontSidePart.CFrame.Position)
		end)
		
		--// Cooldown
		task.wait(60 / inventory.data.Weapon_Settings.FireRate)
		Shoot_Debounce = false
	end
end

function UpdateViewmodel (DeltaTime)
    if is_Shooting and not is_Sprinting and not is_StartSprinting and not is_Reloading and ammo > 0 then
	task.spawn(function()
		Activate_Weapon()
	end)
end

RunService.RenderStepped:Connect(UpdateViewmodel)

I appreciate all the help I can get. This is probably a simple fix, I hope.

4 Likes

.RenderStepped fires every time a frame is rendered, higher fps = event is fired more. You can account for this as explained in this post so that the fps does not affect fire rate

1 Like

DeltaTime is how you fix it. You need to pass DeltaTime into your debounce, since DeltaTime is the amount of time since the previous frame your debounce equation should look like

(60 / FireRate)-DeltaTime

Now i could be wrong because i cant cap my framerate to test but im pretty sure thats how it works.

EDIT: i know its something to do with deltatime

2 Likes

That’s closer. It is still reaching the right firerate on the 240 fps test (700), but on the 60 fps test it is reaching about 600.

1 Like

Weird. Are you getting these values from the server or client?

1 Like

I’m getting them from the server, but they I just tested on the client and it is the same.

Could be a dumb math problem from me. I’m really weak at that so sorry if all of this is wrong.
This is still definitely a delta time problem though. Try using os.clock like this:

local FireRate = 700
local Time = 60 / FireRate
local LastFired = os.clock()

if os.clock()-LastFired >= Time then
	print("Fired")
	LastFired = os.clock()
end

May be more accurate.

1 Like

That still didn’t work. This is kind of weird.

I went back to an old script of mine, updated a few things, and hopefully, this should work for you. However, you’ll have to change some more yourself. It works for semi-auto and full-auto, but I didn’t implement any burst-type stuff.

local timeAccumulator = 0 -- Stores accumulated time

task.spawn(function()
	game:GetService("RunService").RenderStepped:Connect(function(deltaTime)
		local roundsPerMinute = gunset.rpm
		local fireInterval = 60 / roundsPerMinute -- Time per shot in seconds
		
		if shooting and not reloading and not running and ammo > 0 then
			
			if timeAccumulator + deltaTime >= fireInterval then
				shoot()
				timeAccumulator = timeAccumulator - fireInterval
				if not gunset.auto then
					shooting = false
				end
			end
			
			timeAccumulator = timeAccumulator + deltaTime -- Accumulate time
			
			-- Enforce fire rate based on accumulator, independent of FPS
			while timeAccumulator >= fireInterval do
				-- Fire instantly on click and then enforce the fire rate
				shoot()
				--ammo = ammo - 1
				timeAccumulator = timeAccumulator - fireInterval -- Adjust time accumulator for fire rate
			end
		else
			-- Reset accumulator when not shooting
			--timeAccumulator = timeAccumulator + deltaTime
			if timeAccumulator <= fireInterval then
				timeAccumulator = timeAccumulator + deltaTime
			end
		end
		--timeAccumulator = timeAccumulator + deltaTime
	end)
end)```
1 Like

So, I have traced it back to be a problem with my server script, or at least when I fire the event. I’m guessing that it adds a delay, but isn’t task.spawn supposed to create a new thread? I’m sorry if I’m being stupid and this is a simple fix, I’m kind of tired.

function Activate_Weapon()
	if inventory.viewmodel and not Shoot_Debounce then
		Shoot_Debounce = true
		
		RecoilSpring:AddVelocity(inventory.data.VM_Settings.Recoil_Amount)
		--// Actions

		recoil_Goal = CFrame.Angles(0, math.rad(math.random(-2, 2)), 0)
		
		task.spawn(function()
			--Remotes.Events.ActivateWeapon:FireServer(inventory.viewmodel.AimPart.CFrame.Position, inventory.viewmodel.FrontSidePart.CFrame.Position)
		end)
		
		--// Cooldown
		task.wait(60 / inventory.data.Weapon_Settings.FireRate)
		Shoot_Debounce = false
	end
end

I can add my server script if it is needed.

Firing the server WILL add a delay, and spawn won’t technically fix it. You can always handle the rays on the client and verify the information on the server, or you can let the server handle the rays. Either one will present different risks and issues that will have to be verified on the server. Using something like a bindable event will force the client to wait on the server, further adding delay. Remote events would be my recommendation, along with handling rays on the client, especially for visual feedback while verifying the hits and rays on the server.

1 Like

Ok, I just got it fixed! The problem was solved with the delta time applied to the wait that @Daniel1154 pointed out, and I had some code that enables/disables the muzzle effects that would fire every frame. The code that fired every frame was the main problem, I moved it to the Activate_Weapon function and it removed the delay. It was bad practice to update it every frame anyways, I just didn’t think it would delay the script that much. I now feel like an idiot.