Hi guys,
I’m working on a custom Animate script, allowing creators to easily set default and custom animations!
Here’s the script if anyone is interested (prolly not lol)
--[[
# Custom animation script for Roblox #
Using this script, you can easily control the player's animations.
You can set all the default animations using this script, but also overwrite
them using the bindable.
It's also possible to make the character perform an animation using the bindable,
with the possibility of locking, or forcing it. Can be controlled from only the client.
If you place the bindable anywhere other than it's default location, you will
need to reset the correct path in this script.
Futher documentation concerning handling player animations can be found in
the Readme script. Documentation of the Animate script itself can be found
throughout the script itself.
]]--
-- # Settings #
-- Path to the bindable
local bindable = game:GetService("ReplicatedStorage"):WaitForChild("GetAnimate")
-- Transition time for changing animations
local transitionTime = 0.1
-- Speed thresholds
local idleThreshold = 0.1
local runningThreshold = 0.7
-- The number to divide run speed with for normalized animation values
local normalizeFactor = 16
-- Fallback animation
local fallbackAnimation = "idle"
-- Speed to set custom animations to (if not specified through the module or remote)
local customSpeed = 1
-- Allow players to use their own default animations
local allowCustomAnimations = true
-- The animations for the player
local animations = {
-- Default animations, only change Asset ID's here
idle = "http://www.roblox.com/asset/?id=507766388",
walk = "http://www.roblox.com/asset/?id=507777826",
run = "http://www.roblox.com/asset/?id=507767714",
jump = "http://www.roblox.com/asset/?id=507765000",
swimIdle = "http://www.roblox.com/asset/?id=507785072",
swim = "http://www.roblox.com/asset/?id=507784897",
fall = "http://www.roblox.com/asset/?id=507767968",
sit = "http://www.roblox.com/asset/?id=2506281703",
climb = "http://www.roblox.com/asset/?id=507765644",
-- # You can add more custom animations here #
}
-- Get some stuff from the player
local chr = script.Parent
local hmd = chr:WaitForChild("Humanoid")
local animator = hmd:WaitForChild("Animator")
local plr = game:GetService("Players").LocalPlayer
-- Get potentially stored animation data
local http = game:GetService("HttpService")
local data = plr:GetAttribute("AnimateData")
-- Decode and load animate data, if exists
if data then
data = http:JSONDecode(data)
animations = data.animations
end
-- Function to make an animation usable
local loadedAnimations = { }
local function loadAnimation(index, id)
local animation = Instance.new("Animation")
animation.AnimationId = id
animation = animator:LoadAnimation(animation)
animation.Priority = Enum.AnimationPriority.Core
loadedAnimations[index] = animation
end
local coreAnimationNames = {
"climb",
"idle",
"walk",
"jump",
"swim",
"fall",
"run",
-- Add these two for safety checks later on
"unknown",
"disabled"
}
-- Set the custom animations, if allowed
if allowCustomAnimations then
for _, child in pairs(script:GetChildren()) do
if table.find(coreAnimationNames, child.Name) then
local anims = child:GetChildren()
if anims[1]:IsA("Animation") then
animations[child.Name] = anims[1].AnimationId
end
end
end
end
-- Prepare initial animations
for index, id in pairs(animations) do
loadAnimation(index, id)
end
-- Table that contains some state data
local state = {
animation = nil,
curr = "ground",
last = "idle",
locked = false,
speed = 1
}
-- Set default movement animations
local function setDefaultAnimation()
if not state.locked and state.curr ~= "disabled" then
-- Decide the name of the animation that should be played
local nextAnimation
if state.curr == "ground" then
if state.speed < idleThreshold then
nextAnimation = "idle"
elseif state.speed > runningThreshold then
nextAnimation = "run"
else
nextAnimation = "walk"
end
elseif state.curr == "water" then
if state.speed < idleThreshold then
nextAnimation = "swimIdle"
else
nextAnimation = "swim"
end
else
-- Either matches with the current state, or doesn't work and returns nil
nextAnimation = loadedAnimations[state.curr]
end
-- Don't update if it's the same animation
if nextAnimation ~= state.last then
if state.animation then
state.animation:Stop(transitionTime)
end
state.last = nextAnimation
state.animation = nextAnimation and loadedAnimations[nextAnimation] or loadedAnimations[fallbackAnimation]
state.animation:Play(transitionTime)
end
end
end
setDefaultAnimation()
-- Call to update the animation speed
local function updateSpeed(speed, dontSetAnimation)
-- Set speed to 16 (default value) when it shouldn't be influenced, or return when no changes are necessary
if speed == nil then
if state.curr == "air" or state.curr == "sit" or state.curr == "unknown" then
speed = 1
else
speed = state.speed
end
else
speed /= normalizeFactor
end
state.speed = speed
if state.animation and not state.locked then
state.animation:AdjustSpeed(speed)
end
if not dontSetAnimation then
setDefaultAnimation()
end
end
-- Function callable through module to flexibly use custom animations
local function setCustomAnimation(animationName, looped, loops, locked, forced)
local animation = loadedAnimations[animationName]
if animation then
if forced or not state.locked then
state.locked = locked
if state.animation then
state.animation:Stop(transitionTime)
end
updateSpeed(customSpeed, true)
state.last = animationName
state.animation = animation
state.animation:Play(transitionTime)
if locked or not looped then
if loops >= 0 then
-- Stop the loop after the specified amount of loops
while loops > 0 do
loops -= 1
state.animation.DidLoop:Wait()
end
state.animation:Stop(transitionTime)
state.locked = false
updateSpeed(nil)
else
-- Disable lock to make sure it won't be looped forever
print("Nuh uh locked")
state.locked = false
end
end
end
else
warn("Animation '" .. animationName .. "' was not found!")
print(animations)
end
end
-- Table for storing functions to send to scripts, make shared using __index to save data
local customFunctions = {}
-- Function to add animations not initially specified in the script
function customFunctions:Add(animationName, animationId)
if not table.find(coreAnimationNames, animationName) then
-- Format ID in case it's send as a number
if type(animationId) == "number" then
animationId = "http://www.roblox.com/asset/?id=" .. tostring(animationId)
end
animations[animationName] = animationId
loadAnimation(animationName, animationId)
else
warn("Cannot overwrite core animation!")
end
end
-- Store commands to fire frequently used animations more conveniently
local storedCommands = data and data.storage or { }
function customFunctions:Store(commandName, storeAcrossReset, animationName, looped, loops, locked, forced)
storedCommands[commandName] = {storeAcrossReset, animationName, looped, loops, locked, forced}
-- Decide whether to store the returned function for use after the player has died.
if storeAcrossReset then
_G.AnimateFunctions[commandName] = function()
setCustomAnimation(animationName, looped, loops, locked, forced)
end
return function()
_G.AnimateFunctions[commandName]()
end
else
return function()
setCustomAnimation(animationName, looped, loops, locked, forced)
end
end
end
-- Function to execute stored commands, alternative for using the returned function from :Store()
function customFunctions:Load(commandName)
local command = storedCommands[commandName]
if command then
setCustomAnimation(command[2], command[3], command[4], command[5], command[6])
else
warn("Command '" .. commandName .. "' not found!")
end
end
-- Resume the functions preserved for work after reset
if not _G.AnimateFunctions then
_G.AnimateFunctions = {}
else
for commandName, _ in pairs(_G.AnimateFunctions) do
_G.AnimateFunctions[commandName] = function()
local command = storedCommands[commandName]
if command then
setCustomAnimation(command[2], command[3], command[4], command[5], command[6])
else
warn("Command '" .. commandName .. "' not found!")
end
end
end
end
-- Function to play an animation that isn't stored
function customFunctions:Play(...)
setCustomAnimation(...)
end
-- Update the functions to that of the current Animate script, and set status to active
customFunctions.Active = true
_G.AnimateCustom = customFunctions
-- Build the functions that will point towards the updated functions
local pointerFunctions = {}
function pointerFunctions:Add(...)
_G.AnimateCustom:Add(...)
end
function pointerFunctions:Store(...)
-- Has a return to return the function built by :Store()
return _G.AnimateCustom:Store(...)
end
function pointerFunctions:Load(...)
_G.AnimateCustom:Load(...)
end
function pointerFunctions.IsActive()
return _G.AnimateCustom.Active
end
-- Return table containing functions to calling script
bindable.OnInvoke = function()
return pointerFunctions
end
-- Send custom functions to already connected scripts for continuous use
bindable:Invoke(customFunctions)
-- States and their simplified version to make checking the current state easier
local simplifiedStates = {
[Enum.HumanoidStateType.Running] = "ground",
[Enum.HumanoidStateType.Landed] = "ground",
[Enum.HumanoidStateType.PlatformStanding] = "ground",
[Enum.HumanoidStateType.Jumping] = "fall",
[Enum.HumanoidStateType.Freefall] = "fall",
[Enum.HumanoidStateType.Swimming] = "water",
[Enum.HumanoidStateType.Climbing] = "climb",
[Enum.HumanoidStateType.Seated] = "sit",
[Enum.HumanoidStateType.Dead] = "disabled",
[Enum.HumanoidStateType.Ragdoll] = "disabled",
[Enum.HumanoidStateType.GettingUp] = "disabled",
[Enum.HumanoidStateType.FallingDown] = "disabled"
}
-- Check default state changes
hmd.StateChanged:Connect(function(old, new)
local hasJumped = new == Enum.HumanoidStateType.Jumping
old = simplifiedStates[old] or "unknown"
new = simplifiedStates[new] or "unknown"
-- Only update the state if it has changed
if old ~= new then
state.curr = new
updateSpeed(nil, hasJumped)
if hasJumped then
-- Use ustom animations for jumping, it's more compatible
setCustomAnimation("jump", false, 1)
setCustomAnimation("fall", true, -1)
end
end
end)
-- Update animation speed when player speed changes
hmd.Running:Connect(updateSpeed)
-- Store animation data of the script, to sync across character resets
hmd.Died:Connect(function()
-- Only store the synced commands, the others shouldn't be stored
local syncedCommands = { }
for name, list in pairs(storedCommands) do
if list[1] then
syncedCommands[name] = list
end
end
-- Build data to save and store it to the player
local data = {animations = animations, storage = syncedCommands}
plr:SetAttribute("AnimateData", http:JSONEncode(data))
-- Set status to inactive
_G.AnimateCustom.Active = false
end)