I made a script that is just official Roblox’s scripts(RbxCharacterSounds and Animate) combined into one script plus it is more optimized.
Script advantages over official scripts
improvements:
- faster and less memory usage
- includes R6 and R15 functionality
- smaller
Bugs fixed:
- walk and run animations desynchronization
- jump sound doesn’t always play
- walk/run animation play after character landed (reported by: Scou1yy)
Benchmarks
Character configuration:
- Rig type - R15
- Custom animations - all
- ChatVersiom - LegacyChatService
Memory usage (MB) | Official Roblox scripts | My Script |
---|---|---|
Local player | 0.149-0.171 | 0.054-0.067 |
Local + 1 player | 0.161-0.181 | 0.061-0.074 |
Local + 7 players | 0.212-0.23 | 0.106-0.117 |
Local spawn cost | ~0.338 | ~0.043 |
player spawn cost | ~0.011 | ~0.013 |
Load time (s) | Official Roblox scripts | My Script |
---|---|---|
Local player | 0.0020963 | 0.0011019 |
Local + 1 player | 0.002163 | 0.0011304 |
Local + 7 players | 0.00263 | 0.0013014 |
Local spawn cost | 0.00181 | 0.0009985 |
player spawn cost | 0.0000667 | 0.0000285 |
Benchmarks date: 09.06.2024
Downloads
Optimized animations and sounds controller.rbxm (9.2 KB)
code
--[[
forum post: https://devforum.roblox.com/t/optimized-animations-and-sounds-controller/2525206
author: maksgame19
version: 2.0
]]
local Players = game:FindService("Players") or game:GetService("Players")
local LEGACY_CHAT = (game:FindService("TextChatService") or game:GetService("TextChatService")).ChatVersion == Enum.ChatVersion.LegacyChatService
local ALLOW_CUSTOM_ANIMS = (game:FindService("StarterPlayer") or game:GetService("StarterPlayer")).AllowCustomAnimations
local localPlayer = Players.localPlayer
local LOCAL_PLAYER_ID = localPlayer.UserId
local cachedAnims = {}--stores default animations to prevent making new ones
local plrsFall = {}
local rig
local function disconnectAll(conns)
for _,conn in conns do
conn:Disconnect()
end
end
local function getChildren(conns,parent,func)--easy way of getting instances almost instantly
for _,child in parent:GetChildren() do
func(child)
end
table.insert(conns, parent.ChildAdded:Connect(func))
end
local function createSystem(roots, plrSounds, char, plrID, localPlr)
local humanoid, rootPart = roots.Humanoid, roots.HumanoidRootPart
local minSpeed = humanoid.WalkSpeed * .03
local conns = {}
local oldSound, universalPlay
-- reuse sounds
for _, sound in plrSounds do
sound.Parent = rootPart
sound.RollOffMinDistance = 5
sound.RollOffMaxDistance = 150
sound.Volume = .65
end
local died,freefall,gettingUp,jumping,landing,running,splash,swimming = unpack(plrSounds)
local fallData = {sound=freefall, root=rootPart}
local function playSound(sound)
if oldSound == sound then return end
if oldSound then
oldSound:Pause()
end
if sound then
sound.Playing = true
end
oldSound = sound
end
local stateFuncs = {
[Enum.HumanoidStateType.GettingUp] = function()
gettingUp:Play()
end,
[Enum.HumanoidStateType.Landed] = function()
local velY = -rootPart.assemblyLinearVelocity.Y
if velY <= 75 then return end
landing.Volume = velY*.02-1--map a value from one range to another (velY - 50)*(1 - 0)/(100 - 50) + 0
landing:Play()
end,
[Enum.HumanoidStateType.Climbing] = function()
running.PlaybackSpeed = 1
end,
[Enum.HumanoidStateType.Running] = function()
running.PlaybackSpeed = 1.85
end,
[Enum.HumanoidStateType.Swimming] = function()
playSound(swimming)
local vel = math.abs(rootPart.assemblyLinearVelocity.Y)
if vel > .1 then
splash.Volume = vel*.00288-.008 --map a value from one range to another (velY - 100)*(1 - .28)/(350 - 100) + .28
splash:Play()
end
end,
[Enum.HumanoidStateType.Dead] = function()
died:Play()
end
}
table.insert(conns, freefall.Paused:Connect(function()
plrsFall[plrID] = nil
end))
table.insert(conns, humanoid:GetPropertyChangedSignal("WalkSpeed"):Connect(function()
minSpeed = humanoid.WalkSpeed * .03
end))
humanoid.StateChanged:Connect(function(_, state)--state tracker
(stateFuncs[state] or universalPlay)()
end)
char.AncestryChanged:Once(function()--clear
plrsFall[plrID] = nil
universalPlay()
disconnectAll(conns)
end)
if not localPlr then
universalPlay = playSound
stateFuncs[Enum.HumanoidStateType.Jumping] = function()
jumping:play()
end
stateFuncs[Enum.HumanoidStateType.Freefall] = function()
freefall.Volume = 0
playSound(freefall)
plrsFall[plrID] = fallData
end
--connections
humanoid.climbing:Connect(function(vel)
playSound(vel ~= 0 and running)
end)
table.insert(conns, humanoid.running:Connect(function(vel)--for some reason running event isn't disconnected automaticly so we need to do that manualy
playSound(vel > minSpeed and running)
end))
return
end
--local player handler
local animator = roots.Animator
local anims, emotes, oldAnim, defaultTool, limiter, fallTransitionTime
stateFuncs[Enum.HumanoidStateType.Jumping] = function()
universalPlay(nil, "jump", .1)
jumping:Play()
end
stateFuncs[Enum.HumanoidStateType.Seated] = function()
universalPlay(nil, "seat", .5)
end
stateFuncs[Enum.HumanoidStateType.Freefall] = function()
local jump = anims.jump
if jump.IsPlaying then
limiter = true
task.wait(.25)
if not (jump.IsPlaying and limiter) then return end
limiter = nil
end
freefall.Volume = 0
universalPlay(freefall, "fall", fallTransitionTime)
plrsFall[plrID] = fallData
end
function universalPlay(sound, anim, transitionTime)
playSound(sound)
anim = anims[anim]
if oldAnim ~= anim then
if oldAnim then
oldAnim:Stop(transitionTime)
end
if anim then
anim:Play(transitionTime)
end
oldAnim = anim
end
return anim
end
local function startEmote(emote)
if not emote then return true end
universalPlay()
oldAnim = emote
emote:Play(0.1)
if emote.Looped then return end
local idx = #conns + 1
conns[idx] = emote.Stopped:Once(function()
conns[idx] = nil
task.wait()
if oldAnim == emote then
universalPlay(nil, "idle1", .1)
end
end)
end
local function loadAnim(anim, priority)
anim = animator:LoadAnimation(anim)
anim.Priority = priority or Enum.AnimationPriority.Core
return anim
end
local function makeAnim(anims, priority)
for animName, anim in anims do
local existingAnim = cachedAnims[animName]
if existingAnim then
anim = existingAnim
else
local newAnim = Instance.new("Animation")
newAnim.AnimationId = "http://www.roblox.com/asset/?id=" .. anim
anim = newAnim
cachedAnims[animName] = newAnim
end
anims[animName] = loadAnim(anim, priority)
end
return anims
end
local function idleLoop()
table.insert(conns, anims.idle1.DidLoop:Connect(function()
if math.random(1, 10) ~= 1 then return end
universalPlay(nil,"idle2", .1)
end))
table.insert(conns, anims.idle2.DidLoop:connect(function()
universalPlay(nil,"idle1", .1)
end))
end
if rig ~= humanoid.RigType.Name then--remove unused animations
rig = humanoid.RigType.Name
table.clear(cachedAnims)
end
if LEGACY_CHAT and rig == "R15" or not LEGACY_CHAT then--emote executor
roots.PlayEmote.OnInvoke = function(emote)
if not (anims.idle2.IsPlaying or anims.idle1.IsPlaying) then return end
local loadedEmote
if emote:IsA("Instance") then
loadedEmote = loadAnim(emote)
loadedEmote.Looped = false
else
loadedEmote = emotes[emote]
end
return not startEmote(loadedEmote)
end
end
if LEGACY_CHAT then--emote executor
table.insert(conns, localPlayer.Chatted:connect(function(msg)
if string.sub(msg, 1, 1) ~= "/" or not (anims.idle2.IsPlaying or anims.idle1.IsPlaying) then return end
msg = string.lower(msg)
if string.sub(msg, 2, 3) == "e " then
startEmote(emotes[string.sub(msg, 4)])
elseif string.sub(msg, 2, 7) == "emote " then
startEmote(emotes[string.sub(msg, 8)])
end
end))
end
if rig == "R15" then
local scale = 2 / humanoid.HipHeight
fallTransitionTime = .2
emotes = makeAnim({cheer=507770677,dance=507771019,dance2=507776043,dance3=507777268,laugh=507770818,point=507770453,wave=507770239})
defaultTool = makeAnim({507768375}, Enum.AnimationPriority.Idle)[1]
local function adjustWalkAndRunProperties(walkWaight, runWaight, speed)
local walk, run = anims.walk, anims.run
walk:AdjustWeight(walkWaight)
run:AdjustWeight(runWaight)
walk:AdjustSpeed(speed)
run:AdjustSpeed(speed)
end
do--Animations loader
local humanDesc = roots.HumanoidDescription
local animPack = {climb=507765644,fall=507767968,idle1=507766388,idle2=507766666,jump=507765000,run=913376220,walk=913402848,swim=913384386,swimidle=913389285,seated=2506281703}
local requiredAmount,animationsToFind
local function Continue()
idleLoop()
table.insert(conns, anims.run.Stopped:Connect(function()
anims.walk:Stop()
end))
end
if ALLOW_CUSTOM_ANIMS and humanDesc and (function()
animationsToFind = {climb="ClimbAnimation",fall="FallAnimation",idle="IdleAnimation","IdleAnimation",jump="JumpAnimation",run="RunAnimation",swim="SwimAnimation",swimidle="SwimAnimation",walk="WalkAnimation"}
requiredAmount = 0
for name,property in animationsToFind do
if humanDesc[property] == 0 then
animationsToFind[name] = nil
continue
end
requiredAmount += 1
animPack[name] = nil
if name == "idle" then
animPack.idle1, animPack.idle2 = nil
end
end
animationsToFind[1] = nil
return requiredAmount ~= 0
end)() then
--search/wait for custom animations
local conns = {}
local allAnimsFound
anims = makeAnim(animPack)
local function AnimFound(childName, child)
cachedAnims[childName] = nil --removes unused animation
anims[childName] = loadAnim(child)
requiredAmount -= 1
if requiredAmount ~= 0 then return end
allAnimsFound = true
disconnectAll(conns)
Continue()
end
getChildren(conns, roots.Animate, function(child)
local childName = child.Name
if not animationsToFind[childName] then return end
getChildren(conns, child, function(child)
if childName ~= "idle" then
AnimFound(childName, child)
return
end
getChildren(conns, child, function(waight)
AnimFound(waight.Value == 9 and "idle1" or "idle2",child)
end)
end)
end)
task.spawn(function()
while task.wait(4) and not allAnimsFound do
if char and char.Parent then continue end
disconnectAll(conns)
return
end
end)
else
anims = makeAnim(animPack)
Continue()
end
end
--connections
table.insert(conns, humanoid:GetPropertyChangedSignal("HipHeight"):Connect(function()
scale = 2 / humanoid.HipHeight
end))
table.insert(conns, humanoid.running:Connect(function(vel)--run/walk speed formula 2/HipHeight*.078125*vel
if vel <= minSpeed then
universalPlay(nil, "idle1", .2)
return
end
universalPlay(running, "run", .2)
local walk = anims.walk
if not walk.IsPlaying then
walk:Play(.2)
end
vel *= scale * .078125--scale * (1.25 / 16) * vel
if vel <= .5 then
adjustWalkAndRunProperties(1, .0001, vel * 2)
elseif vel < 1 then
vel = vel * 2 - 1
adjustWalkAndRunProperties(1 - vel, vel , 1)
else
adjustWalkAndRunProperties(.0001, 1, vel)
end
end))
table.insert(conns, humanoid.climbing:Connect(function(vel)
if vel == 0 then
universalPlay(nil, "climb"):AdjustSpeed(0)
return
end
universalPlay(running, "climb", .1):AdjustSpeed(vel * scale * .2)
end))
table.insert(conns, humanoid.swimming:Connect(function(vel)
vel *= scale
if vel <= 1 then
universalPlay(swimming, "swimidle", .4)
return
end
universalPlay(swimming, "swim", .4):AdjustSpeed(vel * .1)
end))
else--R6
fallTransitionTime = .3
--load needed animation
anims=makeAnim({climb=180436334,fall=180436148,idle1=180435571,idle2=180435792,jump=125750702,run=180426354,seat=178130996})
emotes=makeAnim({cheer=129423030,dance=182435998,dance2=182436842,dance3=182436935,laugh=129423131,point=128853357,wave=128777973})
defaultTool=makeAnim({182393478}, Enum.AnimationPriority.Idle)[1]
stateFuncs[Enum.HumanoidStateType.Swimming] = function()
universalPlay(swimming, "run", .1)
local vel = math.abs(rootPart.assemblyLinearVelocity.Y)
if vel > .1 then
splash.Volume = vel*.00288-.008 --map a value from one range to another (velY - 100)*(1 - .28)/(350 - 100) + .28
splash:Play()
end
end
--connections
table.insert(conns, humanoid.running:Connect(function(vel)
if vel <= minSpeed then
universalPlay(nil, "idle1", .1)
return
end
universalPlay(running, "run", .1):AdjustSpeed(vel / char:GetScale() / 14.5)
end))
table.insert(conns, humanoid.climbing:Connect(function(vel)
if vel == 0 then
universalPlay(nil, "climb"):AdjustSpeed(0)
return
end
universalPlay(running, "climb", .1):AdjustSpeed(vel / char:GetScale() / 12)
end))
idleLoop()
end
for _,emote in {emotes.cheer,emotes.laugh,emotes.point,emotes.wave} do
emote.Looped = false
end
--R15/R6 connection
table.insert(conns, char.ChildAdded:Connect(function(child)
if not (child:IsA("Tool") and child:FindFirstChild("Handle")) then return end
defaultTool:Play()
task.wait()
child.AncestryChanged:Wait()
defaultTool:Stop()
end))
end
local function characterAdded(char, plrId, plrSounds)--instantly get instances that ware parented
local localPlr = plrId == LOCAL_PLAYER_ID
local count = 0
local foundIns, conns = {}, {}
local allInstancesFound, instancesToFind, requiredAmount
if localPlr then
instancesToFind = {Animate=true,Humanoid=true,PlayEmote=true,Animator=true,HumanoidDescription=true,HumanoidRootPart=true}
requiredAmount = 6
else
instancesToFind = {Humanoid=true,HumanoidRootPart=true}
requiredAmount = 2
end
local function checkChild(parent)
local name = parent.Name
if not instancesToFind[name] then return end
foundIns[name] = parent
count += 1
if count == requiredAmount or not foundIns.HumanoidDescription and requiredAmount == 5 then
--all instances found
allInstancesFound = true
disconnectAll(conns)
createSystem(foundIns, plrSounds, char, plrId, localPlr)
--trigger first sound and/or animation
local humanoid = foundIns.Humanoid
local state = humanoid:GetState()
humanoid:ChangeState(Enum.HumanoidStateType.Seated)
task.wait()
humanoid:ChangeState(state)
return
end
getChildren(conns,parent,checkChild)
end
getChildren(conns,char,checkChild)
while task.wait(4) and not allInstancesFound do
if char and char.Parent then continue end
disconnectAll(conns)
return
end
end
local function playerAdded(plr)
local char = plr.Character
local id = plr.UserId
local plrSounds = {}
for idx, soundName in {"uuhhh","action_falling","action_get_up","action_jump","action_jump_land","action_footsteps_plastic","impact_water","action_swim"} do
local sound = Instance.new("Sound")
sound.Name = soundName
sound.RollOffMinDistance = 5
sound.RollOffMaxDistance = 150
sound.Volume = .65
sound.SoundId = "rbxasset://sounds/" .. soundName .. ".mp3"
plrSounds[idx] = sound
end
local swim = plrSounds[8]
swim.Looped = true
swim.PlaybackSpeed = 1.6
plrSounds[2].Looped = true--freefall
plrSounds[6].Looped = true--running
if char then
task.spawn(characterAdded, char, id, plrSounds)
end
plr.CharacterAdded:Connect(function(char)
characterAdded(char, id, plrSounds)
end)
end
for _, plr in Players:GetChildren() do
playerAdded(plr)
end
Players.PlayerAdded:Connect(playerAdded)
Players.PlayerRemoving:Connect(function(plr)
plrsFall[plr.UserId] = nil
end)
;(game:FindService("RunService") or game:GetService("RunService")).Heartbeat:Connect(function(dt)
for id,tab in plrsFall do
local sound = tab.sound
if tab.root.AssemblyLinearVelocity.Y > -75 then
sound.Volume = 0
continue
end
local volume = sound.Volume+.9*dt
if volume < 1 then
sound.Volume = volume
continue
end
plrsFall[id] = nil
sound.Volume = 1
end
end)