I’m having an issue with using Spring modules for visual effects like sway, bobbing, and recoil in my game. The behavior of these effects changes based on the player’s FPS:
High FPS: Effects become excessively fast and intense.
Low FPS: Effects become slow and less noticeable.
What I’ve Tried
Different Loop Methods: I’ve tried RenderStepped, Stepped and Heartbeat, but none of them kept the effects consistent across different FPS rates.
Using clock Parameter (with os.clock): I set up the Spring to use os.clock in the time parameter to aim for a real-time update. However, the effects still vary with FPS.
Adjusting Delta Time: I also tried multiplying deltaTime (dt) by 60 to adjust it based on the frame rate. I created an adjustedDT variable where adjustedDT = dt * 60 and passed this adjusted value into the Spring functions to try and standardize the effect. However, this didn’t solve the issue.
Other Modules and Solutions: I’ve tested various Spring modules and looked through DevForum posts for solutions, but nothing has fully resolved the issue.
Thanks for the response! I’m looking to control spring effects based on render FPS, not physics FPS. GetRealPhysicsFPS() doesn’t solve it in this case, but I appreciate the suggestion
Use a while loop instead of RunService, which uses CPU/Engine time instead of GPU time.
You only want to use RunService when you WANT to rely on a dynamic clocktime depending on the client’s GPU.
If you want a fixed time, which depends on the CPU clock, which is always static unless the engine isn’t responding, you’re gonna wanna use a while loop, which is what they exist for.
I’ve already tried using a while loop instead of RunService, but the effect becomes broken and inconsistent. I’m still looking for a way to stabilize the spring effects regardless of FPS
The baseplate is already empty for testing purposes, and the code is clean and optimized. However, the issue still persists. Ideally, I would need a way to standardize the delta time to simulate 60 FPS, regardless of the player’s actual FPS. This would keep the spring effects consistent, even if the player’s FPS is high or low. So far, I haven’t found a solution for this…
the issue with using delta time to simulate 60 fps is:
If the player’s fps is higher than 60, its fine; we can do some math for this.
However
If the player’s fps is LOWER, we can’t do anything because it wont be firing enough times.
This function (updateCheckouts) is responsible for updating the effects in real-time, applying the calculated values for sway, recoil, aim, and bobbing. The final adjustments are made in the last LerpBehavior function, where I pass the parameters of each effect. The LerpBehavior.Lerping function manages the specific interpolation and smoothing of these effects, ensuring each one transitions smoothly based on the calculated values.
Function Updates
function weaponStatus:updateCheckouts(deltaTime)
self.deltaTime = deltaTime
if self.managerFPS.viewmodel then
if self.humanoid.WalkSpeed == self.states.running then
self.variables.schemeAim = false
else
self.variables.schemeAim = true
end
local goalBobbing = Vector3.new(
math.sin(tick() * -8 * 1) * 30,
math.cos(tick() * 32 * 0.5) * 0.012,
0
)
self.springs.viewmodelBobbing:SetTarget(goalBobbing)
LerpBehavior.Lerping(weaponData)
end
end
you COULD use a multiplier on your values based on how much time has passed since the last loop
local FPS_60_CLOCK = 1/60
function weaponStatus:updateCheckouts(TimeSinceLastLoop)
local CounterLoopRate = TimeSinceLastLoop/FPS_60_CLOCK --// Create a multiplier based on the time since last loop
if self.managerFPS.viewmodel then
if self.humanoid.WalkSpeed == self.states.running then
self.variables.schemeAim = false
else
self.variables.schemeAim = true
end
local goalBobbing = Vector3.new(
(math.sin(tick() * -8 * 1) * 30) * CounterLoopRate, --// Multiply based on our result multiplier
(math.cos(tick() * 32 * 0.5) * 0.012) * CounterLoopRate, --// Multiply based on our result multiplier
0
)
self.springs.bobbings.viewmodel:shove(goalBobbing)
self.springs.bobbings.viewmodel:update(deltaTime)
LerpBehavior.Lerping(weaponData)
end
end
local LastLoopClock = tick()
while task.wait() do
local CurrentTick = tick()
weaponStatus:updateCheckouts(CurrentTick-LastLoopClock)
LastLoopClock = CurrentTick
end
I tested with the module that requires shove and update, but the issue persists. I also tried Quenty’s module, which doesn’t rely on delta time, yet somehow the player’s FPS still affects the effects. I can’t understand why FPS influences the behavior, considering the module shouldn’t depend on delta time. Additionally, using loops like while only breaks the effects and doesn’t solve the issue. Thanks for the suggestion and support so far.
After three days of attempts, I realized that the Lerp function was causing FPS dependency in my effects, leading to inconsistent results. I fixed this by multiplying the interpolation speed by deltaTime * 60. This adjustment normalizes the speed, ensuring that the effect remains stable regardless of the frame rate. The reason behind this is that deltaTime represents the time between frames (pretty obvious), so multiplying it by 60 adjusts the interpolation to simulate a 60 FPS rate, resulting in a consistent experience.
Adendum: If you’re trying to normalize weapon recoil effects and ensure consistent force regardless of the player’s FPS, here’s a tip that worked for me. Multiply the recoil vector by deltaTime multiplied by 60, as shown in the example below:
function weaponStatus:updateRecoil(rc)
self.springs.camRecoil:SetTarget(Vector3.new(rc / 35, 0, math.random(-rc, rc) / 10) * self.deltaTime * 60)
task.delay(.015, function()
self.springs.camRecoil:SetTarget(Vector3.zero)
end)
end