Wait() Based on Framerate

Is there an other way to yield a thread for a specific amount of time other than using wait()? I am working on a first person shooter game, and I noticed that players who are laggy shoot their automatic weapons more slowly, whereas people who use things like FPS unlocker shoot faster. It seems that the wait function internally checks how much time has elapsed since it was called every OTHER frame, rather than every frame, which means wait(0.06) doesn’t actually wait for 0.06 seconds…more like 5 or 6 frames. And since wait() is based on your framerate, it causes problems when the framerate is above or below 60 fps. I’m just frustrated with this because I always assumed that wait(x) would yield the thread for EXACTLY x amount of time. There is little to no documentation on the roblox wiki for wait(), so it was my surprise when I found out that wait() is not very accurate at all.

3 Likes

A wait within the server, and make a flag system on the server too.
The client pings the server, the server flicks a flag, fires a round then ends.
If the server is pinged again too soon, it’ll check the flag.

2 Likes

You can try using Runservice:

game:GetService(“RunService”).Heartbeat:Wait()

It’s guaranteed to run 60 times a second.

1 Like

(Note that is way more fitting in #scripting-support)

Honestly, your question is very spot on, and has been a conversation for a rather long time. Useful link 1, Useful link 2.
Pretty much, almost as you said, the amount of time wait() waits depends on your performance, if the CPU is currently doing a lot of operations, thus making wait() wait longer than expected. And even when the CPU isn’t exhausted, wait() is almost always off by a certain but. There are tricks that you can do, like for example the delta time (dt, change in time) parameter which RenderStepped provides, gives you the amount of time that passed since the last frame. If at a certain frame the CPU was exhausted, which means the time till the next frame is rendered is longer, there would be a delay, stuff happening late because more time is used. (The second link explains this wonderfully). Let’s say you wanted to increment a value by 1 each second, RenderStepped will make the incrementing work perfectly, using the dt (delta time) parameter which it provides, which is basically how much time passed since the mast frame rendered (in seconds), if the game lags, the longer the time dor the next frame to render to be rendered. You set the value that you wanna increment to 2*dt, the increment times how much time passed. This is good practice. Let’s say exactly 1 second passed since the last frame was rendered (in real cases it would always be a tiny fraction of a second), you essentially multiply 2 by 1 to get 2, if the frame took 0.5 seconds to load you get 1, because half the time passed. This is useful for cases where lag occurs. If the next frame took 2 seconds, you increment by 4. We incremented the value according to how much time was passed, so even though it’s lagging, the values are set to the expected result, if we didn’t multiply by dt, we would wait 2 seconds and just add 2, and not 4 as we would expect in a lag-free game. I hope you understand.

If you’re looking for a better wait, here it is.


local RunService = game:GetService("RunService")

local function wait(n)
    local dt = 0

    while dt < n do
        dt = dt + RunService.Heartbeat:Wait()
    end
    return dt
end
4 Likes

No. With any yielding wait function, such as Wait(), Heartbeat, RenderStepped, etc., you need to get the Delta returned. All devices (and the server) run at different speeds, AND those speeds can change at any time. I’ve found even binding to the frame rate can vary up to 0.4 seconds. For fps games, this matters.

wait() and all other waiting functions in RunService return a value (delta) that tells you how much time passes. For example:

--!strict
-- The above line is your choice, it's for luau
-- This will count to 5 will an incredibly accurate timing

local runService = game:GetService("RunService")
local renderStep = runService.RenderStepped -- Based on frame rate
local timer: number = 0 -- If you don't use LuaU, just type: local timer = 0

while timer < 5 do -- Five seconds
   local delta = renderStep:Wait()
   timer = timer + delta
end
4 Likes

This is almost identical to the built-in wait() function. For the server, there isn’t much of a fix for this.
For the client, RenderStepped may be better.

1 Like

I’ve moved this thread to #help-and-feedback:scripting-support as the #help-and-feedback:cool-creations category is more fitting for posts containing any interesting projects or creations that you’d like to show off or get feedback for - not support!

Thanks for the replies! I figured there wasn’t an easy way out. I’ll follow your advice and use a RunService.RenderStepped loop to keep track of time accumulated between shots. I guess the advantage of that is that I can check how much time has accumulated every frame rather than every 2 frames. Still, I wonder how games like Phantom Forces time their guns to not have noticeable changes in firerate when a user’s framerate changes. Or maybe I haven’t paid close enough attention, haha.

1 Like