I would be pretty surprised if someone actually reply to this
I have been stuck on solving this problem for nearly 2 weeks for now and I really couldn’t come up with a consistent solution,
The problem:
Suppose there is a number accuracy, it represents the weapon’s accuracy, the degree that the bullets can be spread on. The larger it is, the more inaccurate the weapon is. 0 means no spread, aka perfect accuracy.
When the weapon is fired, it will add a certain amount to accuracy, then it waits for a short amount of time, and slowly returns back to 0.
Procedural code:
-- fires weapon
Weapon.Accuracy += 1
wait(resetTime)
local con
con = RunService.Stepped:Connect(function(_, dt)
if Weapon.Accuracy <= 0 then
con:Disconnect()
return
end
-- Currently I have set the reduce rate to 3 `accuracy` per second.
Weapon.Accuracy -= math.max(3 * dt, 0)
end)
However, there are 2 problems:
First problem: wait(t) is frame-rate dependant and it runs on 30 tick only. In my case, most of the time t will be a quite precise number with decimals (e.g: 0.11) and wait(t) waits at least 1/30 ~= 0.033.
For example, resetTime is 0.11. I would like the game to wait 0.11 seconds before starting to reduce accuracy back to 0.
However, if the game runs on a 60 FPS basis, it will have to wait 0.11 / (1/60) = 6.6 frames, which is impossible as the game doesn’t run in between frames. So I have made it to wait for 7 frames, then compensate back the missing 0.4 frames to accuracy.
local elapsed = 0
local timeToWait = 0.116
local Compensation = 0
con = RunService.Stepped:Connect(function(_, dt)
elapsed += dt
if elapsed >= timeToWait then
if Weapon.Accuracy <= 0 then
con:Disconnect()
return
end
if Compensation then
Compensation = elapsed - timeToWait
end
Weapon.Accuracy -= math.max(3 * (dt + (Compensation or 0)), 0)
Compensation = nil -- only compensate once
end
end)
Second problem:
Since I have multiplied delta time (3 * dt), the accuracy will be reduced in a synchronized rate in all framerates. This is how it works on 60 FPS vs 120 FPS:
Red line represents frames, green text represents accuracy.
However, there is one thing that is not synchronized:
Suppose the weapon was fired again during the accuracy reduction process. In 120 FPS, the player gets more frames to fire the weapon. If it was fired on a non-60 FPS frame, the Accuracy will become slightly larger, comparing to 60 FPS.
As shown from the sketch below, the weapon accuracy will become 1.875 on 120 FPS, while it should be 1.85. This might not seem to be a huge difference but if the weapon was being fired rapidly or the user is on even higher FPS, the difference is very noticeable.
I have thought of few solutions, and to be compatible to my framework, such as other parts of the code are going to use the accuracy value, I have decided to update accuracy on a 60 tick rate.
Here is what I have got so far, basically I have make it not to update whenever it’s not on 60 frames, as well as still updating on 60 tick even if user is below 60 FPS.
However, it still does not work quite precisely on different framerates, the values are still different and varies in different frame-rate.
Here’s my entire prototype that includes a yield function to simulate weapon fire pausing:
while wait(3) do
local RunService = game:GetService("RunService")
local Weapon = {Accuracy = 0}
local elapsed = 0
local timeToWait = 0.017
local Compensation = 0
local elapsed_reduceStarted = 0
local delta60Timer = 0
local fireTag = tick()
local tag = fireTag
local function roundNumber(num, dec)
return tonumber(string.format("%." .. (dec or 0) .. "f", num))
end
local function yield(duration)
local frame = 0
local frameToWait
local elapsed = 0
repeat
local dt = RunService.RenderStepped:Wait()
elapsed += dt
frame += 1
frameToWait = roundNumber(duration / (elapsed / frame))
until frame >= frameToWait
or elapsed >= duration
warn("yield frame waited", frame)
warn("yield elapsed", elapsed)
return elapsed - duration
end
Weapon.Accuracy += 1 -- fire weapon
con = RunService.Stepped:Connect(function(_, dt)
elapsed += dt
if elapsed >= timeToWait then
if Weapon.Accuracy <= 0 then
con:Disconnect()
return
end
-- overlayed, stop reducing
if fireTag ~= tag then
warn("Overlayed", Weapon.Accuracy)
con:Disconnect()
return
end
if Compensation and elapsed - timeToWait > 0.001 then -- does not have to be too precise or else it ruins the number integrity
Compensation = elapsed - timeToWait
print("Added compensation", Compensation)
end
elapsed_reduceStarted += dt
if elapsed_reduceStarted > delta60Timer then
delta60Timer += (1/60)
-- frame passed during the process
-- e.g: we still want the code to be updated on a 60hz rate even if the user is on 30hz
-- if the user is on 30hz, we update twice as 60/30 = 2
local framesDropped = elapsed_reduceStarted / delta60Timer
Weapon.Accuracy -= (3 * (1/60)) *
(framesDropped >= 2 and math.floor(framesDropped) or 1) -- update twice if lower than 30 FPS
+ (3 * (Compensation or 0))
Compensation = nil -- only compensate once
else
warn("do not update")
end
end
print(Weapon.Accuracy)
end)
yield(0.117) -- simulate fire-rate, suppose the weapon fires every 0.116s
fireTag = tick() -- fire again
end
A place file for those who are interested to solve, same as the code above.
accuracy-prototype.rbxl (30.5 KB)


