I’ve Imagined a Speed Run 4 type system while reading through this. What you have here really serves as a strong foundation for stackable, temporary gameplay effects - each acting as an independent contributor to shared properties like WalkSpeed or FieldOfView.
To answer your earlier point about “increasing a value without overwriting it”, what we’re really talking about is changing state rather than resetting it entirely. The concept of mutating state rather than replacing it.
Player.Leaderstats.Coins.Value = 100
Here in this manner, You’ve just overwritten whatever their old value was. This is a complete reassignment - a total replacement. The previous value is lost.
But if what you mean to do is increase that number, carrying its state forward, then you need to read it first, and then build upon it:
Player.Leaderstats.Coins.Value = Player.Leaderstats.Coins.Value + 10
If you’re manipulating data often, like experience points or cash, a good practice is to encapsulate the increase logic into a function that ensures you can safely modify without overwriting.
In your original setup, you were running a function every single frame, even when nothing was changing. That’s not wrong, but it’s wasteful. Using signals instead lets you respond only when values actually update, which helps cut unnecessary work.
Switching to tables for storing modifier data instead of relying on NumberValue instances is another great move as ZacAttackk mentioned earlier. It reduces overhead (even if minimal) and makes your code more straightforward.
local Event = ReplicatedStorage:WaitForChild("Event")
local Modifiers = {
Speed = { Base = 16 },
Fov = { Base = 70 }
}
local function GetTotal(tbl)
local total = 0
for _, v in pairs(tbl) do
if type(v) == "number" then
total += v
end
end
return total
end
local function Update()
Humanoid.WalkSpeed = GetTotal(Modifiers.Speed)
Camera.FieldOfView = GetTotal(Modifiers.Fov)
end
local function TweenModifier(TableRef, Key, StartValue, EndValue, Duration, EasingStyle, EasingDir)
TableRef[Key] = StartValue
Update()
local TweenInfoObj = TweenInfo.new(
Duration,
EasingStyle or Enum.EasingStyle.Quad,
EasingDir or Enum.EasingDirection.Out
)
local proxy = { Value = StartValue }
local tween = TweenService:Create(proxy, TweenInfoObj, { Value = EndValue })
tween:GetPropertyChangedSignal("Value"):Connect(function()
TableRef[Key] = proxy.Value
Update()
end)
return tween
end
local function ApplyBoost(SpeedIncrease, FovIncrease, Duration)
local inTweenSpeed = TweenModifier(Modifiers.Speed, "Boost", 0, SpeedIncrease, 0.4)
local inTweenFov = TweenModifier(Modifiers.Fov, "Boost", 0, FovIncrease, 0.4)
local outTweenSpeed = TweenModifier(Modifiers.Speed, "Boost", SpeedIncrease, 0, 0.6, Enum.EasingStyle.Quad, Enum.EasingDirection.In)
local outTweenFov = TweenModifier(Modifiers.Fov, "Boost", FovIncrease, 0, 0.6, Enum.EasingStyle.Quad, Enum.EasingDirection.In)
inTweenSpeed:Play()
inTweenFov:Play()
task.wait(Duration)
outTweenSpeed:Play()
outTweenFov:Play()
task.delay(Duration + 1.0, function()
Modifiers.Speed["Boost"] = nil
Modifiers.Fov["Boost"] = nil
Update()
end)
end
-- // Based on an event, we're applying that duration.
Event.OnClientEvent:Connect(function(data)
if typeof(data) == "table" then
local speed = data.Speed or 0
local fov = data.Fov or 0
local duration = data.Duration or 2
ApplyBoost(speed, fov, duration)
end
end)
Update() -- Initialize
If I’ve understood your query properly, This might be of use.