This can really improve certain games that require different movement speed and can change at anytime. It would keep the experience pretty natural.
Thanks!
This can really improve certain games that require different movement speed and can change at anytime. It would keep the experience pretty natural.
Thanks!
Thank you this is very useful.
Small detail but adds a lot! I really like it!
Great resource, I was actually thinking about re-scripting this myself. Roblox needs to re-code the default system as it’s outdated, but for now this will do! ^-^
I believe this was the case back in 2014, where the walking sound’s pitch changed depending on the Humanoid’s WalkSpeed. Anyway, I have a footstep script that uses Raycasting, and it could help you out.
your system is over-complicated, and can be simplified to 17 lines:
local Character = script.Parent
local Humanoid = Character:WaitForChild("Humanoid")
local RootPart = Character:WaitForChild("HumanoidRootPart")
local DefaultSpeed = game:GetService("StarterPlayer").CharacterWalkSpeed
local RunningDivisor = DefaultSpeed / 1.85
local SwimmingDivisor = DefaultSpeed / 1.6
local ClimbingDivisor = DefaultSpeed / 1
Humanoid:GetPropertyChangedSignal("WalkSpeed"):Connect(function()
local WalkSpeed = Humanoid.WalkSpeed
RootPart.Running.PlaybackSpeed = WalkSpeed / RunningDivisor
RootPart.Swimming.PlaybackSpeed = WalkSpeed / SwimmingDivisor
RootPart.Climbing.PlaybackSpeed = WalkSpeed / ClimbingDivisor
end)
it also doesn’t need to be named “RbxCharacterSounds”, nor does it need to be inside of a player.
you’re wasting too much space with your “fancy” variables, useless table entires and incosinstent variable name styling.
hey,
thanks for the criticism but you seem confused
this is a default roblox sound system not mine, and yeah my actual code is around that 17 line that you just wrote
not if you make a custom script but mine is a fork of the default roblox one, so it won’t work otherwise.
I believe you’re referring to the roblox code which I also don’t like, they use deprecated properties/functions and is pretty ugly overall. my code follows the principles of the Lua style guide
so why fork it while you could just dodge all of the hassle and make a custom one?
I don’t like having a lot of tiny scripts scattered around the game, it’s especially bad if a lot of those scripts affect the same thing - character sounds in this case. It’s easier to manage when you handle every part of a specific system in a single script.
it’s not the best to keep everything in a single script, it’s a common mistake people make and it makes it hell to develop more systems.
true
if your script becomes too hard to read/manage you need to split it using ModuleScripts.
by single script I meant a single (Local)Script for each system, not counting modules as they don’t make the management harder, creating another script is what causes issues because if you ever need communication between those two you’ll have to use BindableEvents or another module that could’ve easily been avoided.
anyways, this was a quick contribution in an attempt to improve the default roblox sound script. I don’t want to keep discussing best practices
how do I add multiple footsteps sounds to ur script without breaking it and Great job?
It’s not really overly-complicated in my opinion.
I took a look and it seems pretty good, raycasting down from the character foot is what I use as well for movement particles/dust and other effects.
thanks for sharing!
You’re welcome; I suggest using 1.5-2 for the raycasting distance, seeing as using smaller values will detect more footsteps, though will cause spamming.
Hey man sorry to knock on an old post here, but anytime I try to put a custom sound instead of the default sound, I’m returned with a error stating. that Roblox couldn’t download the sound data, and the id is correct, but when I return it to the default sound it works as normal. would know of any reason why this is happening?
have you tried playing the sound in the explorer without running the game? try pasting the ID in the Sound
instance and see if that gives you the same error.
DM me if you need more help
Here is a newer version with custom footsteps:
--!nonstrict
-- Roblox character sound script
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local AtomicBinding = require(script:WaitForChild("AtomicBinding"))
local function loadFlag(flag: string)
local success, result = pcall(function()
return UserSettings():IsUserFeatureEnabled(flag)
end)
return success and result
end
local SOUND_DATA : { [string]: {[string]: any}} = {
Climbing = {
SoundId = "rbxasset://sounds/action_footsteps_plastic.mp3",
Looped = true,
Pitch = 1,
},
Died = {
SoundId = "rbxasset://sounds/uuhhh.mp3",
},
FreeFalling = {
SoundId = "rbxasset://sounds/action_falling.mp3",
Looped = true,
},
GettingUp = {
SoundId = "rbxasset://sounds/action_get_up.mp3",
},
Jumping = {
SoundId = "rbxassetid://2452829528",
Volume = .03,
},
Landing = {
SoundId = "rbxasset://sounds/action_jump_land.mp3",
},
Running = {
SoundId = "rbxasset://sounds/action_footsteps_plastic.mp3",
Looped = true,
Pitch = 1.85,
},
Splash = {
SoundId = "rbxasset://sounds/impact_water.mp3",
},
Swimming = {
SoundId = "rbxasset://sounds/action_swim.mp3",
Looped = true,
Pitch = 1.6,
},
}
local DEFAULT_RUN_SOUND_SPEED = SOUND_DATA.Running.Pitch
local DEFAULT_SWIM_SOUND_SPEED = SOUND_DATA.Swimming.Pitch
local DEFAULT_CLIMB_SOUND_SPEED = SOUND_DATA.Climbing.Pitch
local DEFAULT_WALKSPEED = 16
local RUNNING_DIVISOR = DEFAULT_WALKSPEED / DEFAULT_RUN_SOUND_SPEED
local SWIMMING_DIVISOR = DEFAULT_WALKSPEED / DEFAULT_SWIM_SOUND_SPEED
local CLIMBING_DIVISOR = DEFAULT_WALKSPEED / DEFAULT_CLIMB_SOUND_SPEED
local MaterialTable = {
[Enum.Material.Plastic] = 315915457;
[Enum.Material.Metal] = 5639245164;
[Enum.Material.Granite] = 3444211679;
[Enum.Material.Fabric] = 133705377;
[Enum.Material.Concrete] = 5761648082;
[Enum.Material.Grass] = 344063420;
[Enum.Material.Ground] = 9083822528;
[Enum.Material.Sand] = 619188333;
[Enum.Material.WoodPlanks] = 9083826864;
[Enum.Material.Wood] = 9083826864
}
local MaterialVol = {
[Enum.Material.Metal] = .05;
[Enum.Material.Granite] = .05;
[Enum.Material.Fabric] = .05;
[Enum.Material.Concrete] = .05;
[Enum.Material.Grass] = .25;
[Enum.Material.Ground] = .35;
[Enum.Material.Sand] = .65;
[Enum.Material.WoodPlanks] = .25;
[Enum.Material.Wood] = .25
}
-- wait for the first of the passed signals to fire
local function waitForFirst(...) -- RBXScriptSignal
local shunt: BindableEvent = Instance.new("BindableEvent")
local slots = {...}
local function fire(...)
for i = 1, #slots do
slots[i]:Disconnect()
end
return shunt:Fire(...)
end
for i = 1, #slots do -- RBXScriptSignal
slots[i] = slots[i]:Connect(fire) -- Change to RBXScriptConnection
end
return shunt.Event:Wait()
end
-- map a value from one range to another
local function map(x: number, inMin: number, inMax: number, outMin: number, outMax: number): number
return (x - inMin)*(outMax - outMin)/(inMax - inMin) + outMin
end
local function playSound(sound: Sound)
sound.TimePosition = 0
sound.Playing = true
end
local function shallowCopy(t)
local out = {}
for k, v in pairs(t) do
out[k] = v
end
return out
end
local function initializeSoundSystem(instances)
local player = instances.player
local humanoid = instances.humanoid
local rootPart = instances.rootPart
local sounds: {[string]: Sound} = {}
-- initialize sounds
for name: string, props: {[string]: any} in pairs(SOUND_DATA) do
local sound: Sound = Instance.new("Sound")
sound.Name = name
-- set default values
sound.Archivable = false
sound.RollOffMinDistance = 5
sound.RollOffMaxDistance = 150
sound.Volume = 0.65
for propName, propValue: any in pairs(props) do
(sound :: any)[propName] = propValue
end
sound.Parent = rootPart
sounds[name] = sound
end
local playingLoopedSounds: {[Sound]: boolean?} = {}
local function stopPlayingLoopedSounds(except: Sound?)
for sound in pairs(shallowCopy(playingLoopedSounds)) do
if sound ~= except then
sound.Playing = false
playingLoopedSounds[sound] = nil
end
end
end
-- state transition callbacks.
local stateTransitions: {[Enum.HumanoidStateType]: () -> ()} = {
[Enum.HumanoidStateType.FallingDown] = function()
stopPlayingLoopedSounds()
end,
[Enum.HumanoidStateType.GettingUp] = function()
stopPlayingLoopedSounds()
playSound(sounds.GettingUp)
end,
[Enum.HumanoidStateType.Jumping] = function()
stopPlayingLoopedSounds()
playSound(sounds.Jumping)
end,
[Enum.HumanoidStateType.Swimming] = function()
local verticalSpeed = math.abs(rootPart.AssemblyLinearVelocity.Y)
if verticalSpeed > 0.1 then
sounds.Splash.Volume = math.clamp(map(verticalSpeed, 100, 350, 0.28, 1), 0, 1)
playSound(sounds.Splash)
end
stopPlayingLoopedSounds(sounds.Swimming)
sounds.Swimming.Playing = true
playingLoopedSounds[sounds.Swimming] = true
end,
[Enum.HumanoidStateType.Freefall] = function()
sounds.FreeFalling.Volume = 0
stopPlayingLoopedSounds(sounds.FreeFalling)
playingLoopedSounds[sounds.FreeFalling] = true
end,
[Enum.HumanoidStateType.Landed] = function()
stopPlayingLoopedSounds()
local verticalSpeed = math.abs(rootPart.AssemblyLinearVelocity.Y)
if verticalSpeed > 75 then
sounds.Landing.Volume = math.clamp(map(verticalSpeed, 50, 100, 0, 1), 0, 1)
playSound(sounds.Landing)
end
end,
[Enum.HumanoidStateType.Running] = function()
stopPlayingLoopedSounds(sounds.Running)
sounds.Running.Playing = true
playingLoopedSounds[sounds.Running] = true
end,
[Enum.HumanoidStateType.Climbing] = function()
local sound = sounds.Climbing
if math.abs(rootPart.AssemblyLinearVelocity.Y) > 0.1 then
sound.Playing = true
stopPlayingLoopedSounds(sound)
else
stopPlayingLoopedSounds()
end
playingLoopedSounds[sound] = true
end,
[Enum.HumanoidStateType.Seated] = function()
stopPlayingLoopedSounds()
end,
[Enum.HumanoidStateType.Dead] = function()
stopPlayingLoopedSounds()
playSound(sounds.Died)
end,
}
-- updaters for looped sounds
local loopedSoundUpdaters: {[Sound]: (number, Sound, Vector3) -> ()} = {
[sounds.Climbing] = function(dt: number, sound: Sound, vel: Vector3)
sound.Playing = vel.Magnitude > 0.1
end,
[sounds.FreeFalling] = function(dt: number, sound: Sound, vel: Vector3): ()
if vel.Magnitude > 75 then
sound.Volume = math.clamp(sound.Volume + 0.9*dt, 0, 1)
else
sound.Volume = 0
end
end,
[sounds.Running] = function(dt: number, sound: Sound, vel: Vector3)
sound.Playing = vel.Magnitude > 0.5 and humanoid.MoveDirection.Magnitude > 0.5
end,
}
-- state substitutions to avoid duplicating entries in the state table
local stateRemap: {[Enum.HumanoidStateType]: Enum.HumanoidStateType} = {
[Enum.HumanoidStateType.RunningNoPhysics] = Enum.HumanoidStateType.Running,
}
local activeState: Enum.HumanoidStateType = stateRemap[humanoid:GetState()] or humanoid:GetState()
local function transitionTo(state)
local transitionFunc: () -> () = stateTransitions[state]
if transitionFunc then
transitionFunc()
end
activeState = state
end
transitionTo(activeState)
local stateChangedConn = humanoid.StateChanged:Connect(function(_, state)
state = stateRemap[state] or state
if state ~= activeState then
transitionTo(state)
end
end)
local steppedConn = RunService.Stepped:Connect(function(_, worldDt: number)
-- update looped sounds on stepped
for sound in pairs(playingLoopedSounds) do
local updater: (number, Sound, Vector3) -> () = loopedSoundUpdaters[sound]
if updater then
updater(worldDt, sound, rootPart.AssemblyLinearVelocity)
end
end
end)
local function adjustPlaybackSpeed()
local walkSpeed = humanoid.WalkSpeed
sounds.Running.PlaybackSpeed = walkSpeed / RUNNING_DIVISOR
sounds.Swimming.PlaybackSpeed = walkSpeed / SWIMMING_DIVISOR
sounds.Climbing.PlaybackSpeed = walkSpeed / CLIMBING_DIVISOR
end
local function setRunSound()
local FloorMaterial = humanoid.FloorMaterial
if MaterialTable[FloorMaterial] then
sounds.Running.SoundId = "rbxassetid://"..MaterialTable[FloorMaterial]
sounds.Running.Volume = MaterialVol[FloorMaterial]
else
sounds.Running.SoundId = "rbxasset://sounds/action_footsteps_plastic.mp3"
sounds.Running.Volume = .35
end
end
adjustPlaybackSpeed()
local speedChangedConn = humanoid:GetPropertyChangedSignal('WalkSpeed'):Connect(adjustPlaybackSpeed)
local speedChangedConn = humanoid:GetPropertyChangedSignal('FloorMaterial'):Connect(setRunSound)
local function terminate()
stateChangedConn:Disconnect()
steppedConn:Disconnect()
-- Unparent all sounds and empty sounds table
-- This is needed in order to support the case where initializeSoundSystem might be called more than once for the same player,
-- which might happen in case player character is unparented and parented back on server and reset-children mechanism is active.
for name: string, sound: Sound in pairs(sounds) do
sound:Destroy()
end
table.clear(sounds)
end
return terminate
end
local binding = AtomicBinding.new({
humanoid = "Humanoid",
rootPart = "HumanoidRootPart",
}, initializeSoundSystem)
local playerConnections = {}
local function characterAdded(character)
binding:bindRoot(character)
end
local function characterRemoving(character)
binding:unbindRoot(character)
end
local function playerAdded(player: Player)
local connections = playerConnections[player]
if not connections then
connections = {}
playerConnections[player] = connections
end
if player.Character then
characterAdded(player.Character)
end
table.insert(connections, player.CharacterAdded:Connect(characterAdded))
table.insert(connections, player.CharacterRemoving:Connect(characterRemoving))
end
local function playerRemoving(player: Player)
local connections = playerConnections[player]
if connections then
for _, conn in ipairs(connections) do
conn:Disconnect()
end
playerConnections[player] = nil
end
if player.Character then
characterRemoving(player.Character)
end
end
for _, player in ipairs(Players:GetPlayers()) do
task.spawn(playerAdded, player)
end
Players.PlayerAdded:Connect(playerAdded)
Players.PlayerRemoving:Connect(playerRemoving)
A simpler, more precise solution to all of this is just to insert a named keyframe/marker in your sprinting/walking animation and then play a sound every time that keyframe is reached. This is a surefire way to 100% ensure synced footstep sounds.
Well that is true, you have to make the animation, or go through a hassle to get the official ones and edit them. But yeah, that method produces better results.