Is there a better alternative apart from using wait (1/60)?

My game has a special movement system which allows player to gain movement speed if they timed it right to jump when they land from the previous jump (similar to bhopping). Here’s the pseudo code:

repeat RunService.RenderStepped:Wait() until
       tick() >= Minimum Activate Time or Character Is Jumping
    -- The player jumped again or timed out, player did not jump after he/she has landed.
if -- player jumped again then
    -- Gain Speed
end

Recently I found that players with higher FPS do have an advantage since I am using RenderStepped:Wait() which runs faster when the game runs in a higher FPS.

In other scenarios that have to combat with high FPS issue, the solution will probably be multiplying the values to the delta time such that the code can run smoothly in higher FPS. Such as moving a local part at a same speed but in different FPS. You won’t want a player to see a part moving in 60 FPS while he/she is in a higher FPS.

However, in my case, I’d like to synchronize it to 60 FPS. This is similar how CS:GO has different tick rate servers such that b-hopping is easier on 128 ticks server than 64 ticks. I want the game to count and run the pseudo code above in a “60 FPS rate” such that everyone has a fair chance to use this mechanic. I’m currently thinking changing it from RenderStepped:Wait() into wait(1/60) and I can’t think of any other solution, are there any alternatives?

4 Likes
local function wait(seconds: number)
    seconds = seconds or (1 / 60)
    local deltaTime = 0
    
    while (deltaTime < seconds) do
        deltaTime += game:GetService("RunService").Heartbeat:Wait()
    end

    return deltaTime
end

I’ve always used this function, the good thing about it is that the inaccuracy for it is 0.1, while the inaccuracy for the built-in wait() is about 0.3.
Now, for what you’re looking, this fits because since it uses Heartbeat, it can wait a minimun of 1/60, while wait() can only wait 1/30 because Roblox’s task scheduler runs at 30 Hz.
(I’d also try looking for a non-polling way, for example using the .Jumped event or .StateChanged event)

3 Likes

You have to multiply the speed with delta time since delta time is connected to frame rate, parts won’t move faster/slower frame rate just adds more frames in between position 1 and position 2.

1 Like

This is still FPS based? Apparently it runs faster with a higher FPS though

Hmm let me try to draw out the issue:

With higher fps they get more frames to input a key and gain movement speed
With lower fps they get less frames to input a key and hence suffers as they have time the jump more accurately:

Reminds of krunker io they also have the same issue last time I remember.

image

You could use wait() which could force the minimum wait time 0.02999 (approx. 0.03) (punish the higher fps players decreasing the amount of inputs they have, doesn’t seem like a good idea) but it could also suffer from lag as it’s the minimum wait time so lower fps players still suffers.

Otherwise based on the diagram assuming it’s correct hopefully you could also increase the minimum activate time to compensate for the person with lower fps by adding +1 average frame so they can time to jump easier.

1 Like

Highly recommend you read this great post

That isn’t even relevant to the topic, please reread:

However, in my case, I’d like to synchronize it to 60 FPS.

OP is asking how to synchronize it to 60 FPS, not find a better alternative than RunService.RenderStepped:Wait().

Well, that’s exactly what I’m trying to do! I’m trying to synchronize every player has the same “60 FPS polling” such that higher FPS players does not gain advantage by having more frames to input. I’m trying to make the polling run in a 60 FPS rate despite the player having even higher frames.

If you insist on that, I can think of only one solution:

We have 2 variables: delta60 and deltaCurrent. delta60 is initialized as 1 / 60 and deltaCurrent as 0.
Every time the .RenderStepped event is run, it adds the delta time argument to deltaCurrent, then checks if deltaCurrent < delta60.
If it is true, return. Else, do whatever you need to do, then set deltaCurrent to 0.
As an example, say we have player A at 60 FPS and player B at 120 FPS.

  • First frame:
  • Player A:
    • deltaCurrent(0) + 0.016(1/60) = 0.016
      deltaCurrent(0.016) is bigger/equal than delta60(0.016), continue
    • deltaCurrent = 0
  • Player B:
    • deltaCurrent(0) + 0.0083 (1/120) = 0.0083
      deltaCurrent(0.0083) is smaller than delta60(0.016), return
  • Second frame:
  • Player A:
    • deltaCurrent(0) + 0.016(1/60) = 0.016
      deltaCurrent(0.016) is bigger/equal than delta60(0.016), continue
    • deltaCurrent = 0
  • Player B:
    • deltaCurrent(0.0083) + 0.0083 (1/120) = 0.016
      deltaCurrent(0.016) is bigger/equal than delta60(0.016), continue
    • deltaCurrent = 0

In code, this would be:

local deltaCurrent = 0
local delta60 = (1 / 60)
RunService.RenderStepped:Connect(function(deltaTime: number)
    deltaCurrent += deltaTime
    if (deltaCurrent < delta60) then return end
    
    -- ...
    deltaCurrent = 0
end)

For your implementation, that’d be something like:

local function reproduction()
	local connection: RBXScriptConnection
	
	local deltaCurrent = 0
	local delta60 = (1 / 60)
	connection = RunService.RenderStepped:Connect(function(deltaTime: number)
		deltaCurrent += deltaTime
		if (deltaCurrent < delta60) then return end
		
		-- pseudo code
		if (playerJumping) then
			if (playerJumpingAgain) then
				-- Give them more speed
			end
			
		elseif (tick() >= minimunActivationDistance) then
			connection:Disconnect()
		end
		
		deltaCurrent = 0
	end)
end

But please keep in mind this can make players with FPS of numbers not divisible by 60 have not smooth transitions in whatever you’re doing. Although that’s what you call a “punishment”, I suppose.
The example I gave was in function format, you can adapt that to whatever you need. (^▽^)

1 Like

There’s one small problem with the method above: the player gets 3 frames to jump before it times out on 60 FPS. However with higher FPS, it’s ranged between 3 to 4 FPS and 4 FPS gaps happen often.

I replaced RenderStepped by using :Wait() and it’s somehow solved… not sure is it a good solution tho.
repeat deltaCurrent += RunService.RenderStepped:Wait() if deltaCurrent > delta60 then print("jump now") deltaCurrent = 0 end

1 Like