Trying to force the loop to run at a specific rate is probably what caused your issue in the first place. You should write your scripts to be able to run at any physics rate. That’s part of why functions like wait()
and RenderStepped:Wait()
return the amount of time that was yielded - so you can use that value to accurately move things at the speed you want.
Yes, that’s what I’m trying to do as you can see from my attempts it hasn’t worked out.
You were at least attempting to scale by dt, but doing it wrong. I gave you the correct way to do it in my previous reply:
that just makes it slow in studio and instant in-game.
Edit: wait nvm lol thanks
Nvm it’s doesn’t do anything, I’m trying to make it run at 60 fps no matter what. I might just make a script to kick you if you’re using an fps unlocker because nothings working. https://gyazo.com/7b81ee8d6da5d2013ee4a72601912f68
I don’t understand what your video shows, it looks like it’s working fine to me
The code I posted is correct. position
will increase at the same rate on all computers regardless of your FPS.
Here’s a video of me not using an fps unlocker https://gyazo.com/2594d9203415db5131671176fd1a5026
Here’s a video of me using one https://gyazo.com/912a18c4e0ffb8a68567c8db2ae8cc63
Idk
Where and how do you use position
?
So position is the length of each part in the projectile * number, number is just so I know what position number it is, then position * ray direction gives me the actual position.
You need something more complicated, then.
Flip your thinking around: What happens if one frame takes a whole second to render? Does your code handle that?
You need a way to decouple the number of parts you draw from the number of frames.
For example (this isn’t tested, but it demonstrates the concept – I tried to name variables well so you can tell what they are):
local STUDS_PER_SECOND = 100 -- how many studs the lightning flies in a second
local SEGMENT_LENGTH = 4 -- how long each segment is
local function Fire()
local alreadyDrawn = 0
local position = 0
while true do -- need some condition here
-- get the time elapsed
local dt = game:GetService("RunService").Heartbeat:Wait()
-- the next position to jump to
local nextPosition = position + dt * STUDS_PER_SECOND
-- get how many segments we'll need to draw this frame
local needToDraw = math.floor(nextPosition / SEGMENT_LENGTH) - alreadyDrawn
for i = 1, needToDraw do
local segmentPosition = position + i * SEGMENT_LENGTH
local segmentNumber = alreadyDrawn + i -- if you need it for some reason
-- draw segment using segmentPosition as the position
end
position = nextPosition
alreadyDrawn += needToDraw
end
end
Basically i’ll show you what works in my projectileclass, because a lot of the code you shown seems to look wrong.
Lets get some formulas cleared first:
Distance = Velocity * TotalElapsedTime
or
Distance = Distance + (ElapsedTimeFromLastSetPosition * Velocity)
Second one is better since you can switch velocity in the middle of flight, because it’s instanaous velocity (the velocity at a paticular timeframe)
Now to use the second method here would be an example.
local Hearbeat = game:GetService("RunService").Heartbeat
function NewProjectile()
return {
Velocity = Vector3.new(10,10,0); -- digonal up movement basically the equation: y = x * VELOCITY SO in this case the velocity is 10
Model = workspace.Baseplate;
Position = workspace.Baseplate.Position;
}
end
local MyProjectile = NewProjectile()
Hearbeat:Connect(function(ElapsedTimeFromLastSetPosition)
MyProjectile.Position = MyProjectile.Position + (ElapsedTimeFromLastSetPosition * MyProjectile.Velocity)
MyProjectile.Model.Position = MyProjectile.Position
end)
The smaller the timeframe the smoother it will move because it will move less distance, at even smaller timeframes. (Good compability with FPS unlockers)
Idk how this would work with my projectiles seen as there not even moving or using an physics they just look like there moving.
So you calculate how many segments you need to make. Alr I’ll try it out thanks.
What the script I showed you isn’t with phyiscs is uses math and cframes.
Yeah but I’ve literally done the same thing, it doesn’t work the same for my case.
It works for me try excuting the same exact code I did first and tell me if it works for you, and then modify it.
Ok, but that’s not the problem he’s having? The Heartbeat is a form of a wait. This has nothing to do with the actual problem, a Heartbeat wait is more than enough to not crash the game. This is false information. The reason why OP is having the problem is because RunService events are variable frequency, firing once per frame, meaning the frequency at which they fire is based on the client’s frame rate.
The simple way to fix this is scaling your increments by delta time, as people are explaining above.
Also, I used to have problems with this as well with an old gun system I was messing around with, and I was too lazy to scale the increments by delta time, so you can just use a ratio with delta time since your increments are already for 60 FPS.
Scaling the values to find a ratio like 60 : userFPS is an easy way to scale the values, so it should look the same on every frame rate.
Example: 60 : 120 = 0.5, will make it slower to compensate to look the same, because 120 times a second is double 60, because your increments are made for 60 FPS
Also, you should define RunService outside of the loop since it’s a constant.
local rs = game:GetService("RunService")
local fps
local ratio
local number = 0
while true do
fps = 1/(rs.Heartbeat:Wait())
print(fps)
ratio = 60/fps -- Since it needs to be slower at higher framerates, the number needs to smaller as dt decreases
number = number + 1
local position = ((4 * 100) * number) * ratio
end
Heartbeat and other RunService events are variable frequency, so the frequency which they are fired based on a user’s frame rate.
Your increments are for 60 fps, meaning that it needs to be called 60 times a second for it to look good.
120 FPS is double that, 120 times a second, which is why the speed and stuff is faster. It’s twice as fast.
If you capped to 30, you should see that it gets slower, because it’s called 30 times in a second. Which means it would be about half as fast.
Also, your code is pretty vague, so I’m not sure if this is going to work. Can you provide a larger snippet so I can have more context? How are number and position being used?
The others’ advice to scale the displacement of your projectiles using delta time return values is solid. Although, you really be connecting a function and using the event-based approach instead of a while
or repeat
loop:
local conn
conn = game:GetService("RunService").Stepped:Connect(function (t, dt)
-- Step your projectile forward using dt, as above posts describe...
end)
-- Remember to call conn:Disconnect() once the projectile expires!
Doing it this way also has the benefit of being non-blocking, something that is crucial for good performance and API design.
More importantly…
Something I want to address is using Stepped over Heartbeat. Heartbeat is not the event to use for this case. You should be using Stepped
, 100% here.
- Stepped also runs at around 60 Hz
- Heartbeat runs when the game is paused
- Heartbeat returns only the time since last frame, not the total running seconds
- Stepped fires before physics is processed (which is almost certainly desirable in this case, as it is a projectile system!)
See the Task Scheduler article on the Developer Hub for more info on this. Not to toot my own horn, but I wrote the article.
This isn’t what os.clock
is meant for! Don’t use it in this way. If you really need a timing mechanic like this, use Workspace.DistributedGameTime
instead.
RunService coordinates time already through the Stepped event, which returns timeElapsed, deltaTime
. See my post above for details.