I absolutely second what the person above me said, though I’d also like to add onto it.
There’s two ways to approach this. The quick way is as follows:
To avoid speed effects overriding one another, change walkspeed by multiplying it by a number rather than hardcoding a new value. This allows speed effects to stack.
Or, you could set up a handler for long-term efficiency. This would apply statuses and update a player’s speed. This is important if you plan on making this a long-term/large project.
Just requires 2 modules and 1 controller. SpeedController will access ProfileService (our module) that’ll be used to initialize a player’s stats upon joining a game and create a place for us to store data about a player, such as statuses or speed multipliers. ProfileService will then access SpeedSystem (another module) that would handle updating a player’s speed based on whatever speed multipliers they have active (which we’ll pass into SpeedSystem from ProfileService). We’ll apply speed multipliers via SpeedSystem since it seems like a relevant task for the system to handle.
Here’s what a module script for storing data about a player may look like.
-- just standard stuff for setting up modules
local ProfileService = {}
local SpeedHandler = require(SpeedHandler) -- imagine this is a module script reference
ProfileService.__index = ProfileService -- lets us set up inheritance for efficiency later
local profiles = {} -- We'll store our player profiles here for access later
function ProfileService:setupTracking()
self.movementStats = {}
end
function ProfileService.new(player) -- Our constructor for player data objects
profiles[player] = {}
local self = setmetatable(profiles[player], ProfileService) -- inheritance set up
self.name = player.Name
self.player = player -- just fields for easy access later
-- could add self.character field later and setup tracking so it is updated upon reset
-- We're using proxies and the __newIndex metamethod to track when player speed/jumppower
-- in their profile object changes and then transferring that change to humanoid
self.movementStats = {}
local proxy = {}
setmetatable(proxy, {
__index = self.movementStats,
__newindex = function(tbl, key, value)
local old = self.movementStats[key] -- old value of changed field
if old ~= value then -- check if a new value was assigned
self.movementStats[key] = value
SpeedHandler:updateStats(self, key, value) -- update humanoid stats
end
end
})
self.movementStats = proxy -- just setting up tracking logic w/ the above few lines
self.speedMultipliers = {}
local proxy2 = {}
setmetatable(proxy2, {
__index = self.speedMultipliers,
__newindex = function(tbl, key, value)
self.speedMultipliers[key] = value
SpeedHandler:updateSpeed(self)
end
})
self.speedMultipliers = proxy2
return self
end
--- These methods update our list of speedMultipliers, triggering the __newIndex
-- metamethod that will update our speed based on our new list of multipliers.
-- Also this is why we needed inheritance, these methods would be executed on profile objects
function ProfileService:addSpeedMultiplier(key, multiplier)
self.speedMultipliers[key] = multiplier -- will now automatically update speed
end
function ProfileService:removeSpeedMultiplier(key)
self.speedMultipliers[key] = nil -- updates speed
end
function ProfileService:get(player)
return profiles[player] --- gives profile
end
return ProfileService
Our SpeedHandler may look like:
local SpeedHandler = {}
SpeedHandler.trackedStats = {
speed = "WalkSpeed",
jumpPower = "JumpPower",
}
function SpeedHandler:updateStats(profile, stat, newValue)
local character = profile.player.Character -- you should probs add onto profileService
--- later so you can just access a character field in it
-- maybe even add a method for getting player character and checking if it's nil or not
if not character then return end
local statToUpdate = self.trackedStats[stat]
local humanoid = character:FindFirstChild("Humanoid")
if humanoid and statToUpdate then
humanoid[statToUpdate] = newValue
end
end
function SpeedHandler:updateSpeed(profile)
local multiplier = 1
for _, change in pairs(profile.speedMultipliers) do
multiplier = multiplier * change
end
local speed = profile.movementStats.speed or 16 -- in case of nil
profile.movementStats.speed = speed * multiplier
end
return SpeedHandler
The gist of this was just to show how you could create a speed handler system that updates speed whenever you change the player’s speed value and takes into account all the various things the player is dealing with. Keep in mind the example hasn’t been refactored or tested at all, it’s just me showing you how you might set this up in your own game.
After this, all you’d need to do to apply a slowness effect properly w/o it being overridden is just get the player’s profile and then add a speed multiplier or remove one.
Why go through all of this? Why do all this modular coding when you could just multiply the walkspeed of the humanoid by 0.1 when going through a cobweb and call it a day? Well…
Modular coding makes debugging easier, testing easier, extending on existing systems a matter of adding data rather than restructuring current code, allows for consistent logic across multiple systems, improves abstraction and readability in code, and allows you to divide-and-conquer large tasks.
IMO, if you’re approaching this minecraft game as a long-term project or plan for it to get large, modular programming is how you’ll make your life a lot easier in the long run.
LMK if you have any questions.