Improving fire rate accuracy

If not handled properly, the lower the player’s frame rate is, the slower the player will be able to fire weapons. This issue is prevalent throughout the whole game development industry, yet solutions remain scarce.

The Problem

Consider a weapon with an 800 rounds per minute (RPM) fire rate. A common approach to handling fire rate is as follows:

local fireRate = 800
local fireCooldown = 60 / fireRate

repeat
	-- Fire (raycast, etc.)
	
	task.wait(fireCooldown)
until fireInput == false

However, task.wait may yield for a longer duration than the provided fireCooldown, resulting in a slower fire rate. As a consequence, players running at 60 frames per second (FPS) will shoot at approximately 722 RPM. Oof, that’s 10% less than expected!

800 RPM at 60 FPS resulting in 722 RPM

The Solution

To account for the excess wait time, the previous approach can be adjusted as follows:

local fireRate = 800
local fireCooldown = 60 / fireRate
local excessWaitedTime = 0

repeat
	-- Fire (raycast, etc.)
	
	local timeToWait = fireCooldown - excessWaitedTime
	local waitedTime = task.wait(timeToWait)
	excessWaitedTime = waitedTime - timeToWait
until fireInput == false

This significantly narrows the gap between the actual and expected frame rates, and each iteration occurs at more consistent intervals.

800 RPM at 60 FPS resulting in 798 RPM using the solution


The solution can be explored in more detail in FireRateTest.rbxl (54.9 KB). To get started:

  1. Enter Play mode
  2. Select ReplicatedStorage and configure the attributes as desired
  3. Press and hold the left mouse button to keep firing
33 Likes

This also applies to syncing events with music, on specific beats of the song! Using a basic task.wait(60/bpm) will result in a drift, while using this will account for that.

4 Likes

i’ve had my own version of this which is terrible, i never thought of this until now!

although i suppose this could work better inside a RunService/Stepped/Heartbeat loop as their deltatime parameters are more accurate.