Thank you very much! This was my biggest project so far, took me quite a while to make
Ah I see, started to use this for every game is script for! Very optimized and works like a charm.
Ironic, because I was just revisiting Animate2 last night for my game. I havenât used yours yet but it looks better.
looks good to me, Iâm sure the performance difference is negligible but its much more readable.
Performance wise, I think there is a (maybe small) improvement over the default script even during gameplay, as the only loop present in Animate 2 is the for loop that is used to load the animations to the Animator. Animate 2 is otherwise event based
August 24 Update:
- Added support for Tool Slash and Lunge animations using the StringValue method
- Removed the assert previously found on line 2 in order to make it easier for people to experiment with using Animate 2 for NPCs (Unfortunately I canât guarantee success though, as Iâve only tested Animate 2 on player controlled characters so far)
To be honest, Iâm not that good at naming things. My priorities are to make sure that if I am to publish a product, it behaves as one would expect it to behave (or at the very least, as advertised )
Works on r6? I really need a custom animate script
I canât guarantee it will work for R6, but I did test the R6 zombie NPC and it seemed to be working ok (Although I noticed that the zombie seems to handle the attack animation somewhere different than the default animation script, and I couldnât find where. I couldnât get the attack animation to match the default because of this, but other animations like walking were working ok)
Remember to replace the animation idâs with R6 compatible ones
January 18th, 2024 Update - Animate 2 Version 2:
Bug fixes:
- Found a way to remove the âYou canât use Emotes here.â error that shows up in TextChatService when using the emotes command
- Solved an error that shows up if your experience uses Legacy Chat
Improvements:
- Itâs more convenient now to change the default fade time between AnimationTrack and to customize the fade time for a specific track using the optional
fadeTime
parameter of the newplay
function - Code has been re-written to more closely follow Robloxâs Lua Style guide
- Type checking is now implemented
Animate 2 Version 2 Source Code
--!strict
local TextChatService = game:GetService("TextChatService")
local DEFAULT_FADE_TIME: number = 0.1
local character: Model = script.Parent
local humanoid = character:WaitForChild("Humanoid"):: Humanoid
local animationTracks: {[string]: AnimationTrack} = {}
do
local animator = humanoid:WaitForChild("Animator"):: Animator
local animation = Instance.new("Animation")
local animationData: {[string]: {any}} = {
Cheer = {"rbxassetid://507770677", Enum.AnimationPriority.Idle},
Climb = {"rbxassetid://507765644", Enum.AnimationPriority.Core},
Dance = {"rbxassetid://507772104", Enum.AnimationPriority.Core},
Dance2 = {"rbxassetid://507776879", Enum.AnimationPriority.Core},
Dance3 = {"rbxassetid://507777623", Enum.AnimationPriority.Core},
Fall = {"rbxassetid://507767968", Enum.AnimationPriority.Core},
Idle = {"rbxassetid://507766388", Enum.AnimationPriority.Core},
Laugh = {"rbxassetid://507770818", Enum.AnimationPriority.Idle},
Lunge = {"rbxassetid://522638767", Enum.AnimationPriority.Movement},
Point = {"rbxassetid://507770453", Enum.AnimationPriority.Idle},
Run = {"rbxassetid://913376220", Enum.AnimationPriority.Core},
Sit = {"rbxassetid://2506281703", Enum.AnimationPriority.Core},
Slash = {"rbxassetid://522635514", Enum.AnimationPriority.Movement},
Swim = {"rbxassetid://913384386", Enum.AnimationPriority.Core},
SwimIdle = {"rbxassetid://913389285", Enum.AnimationPriority.Core},
Tool = {"rbxassetid://507768375", Enum.AnimationPriority.Idle},
Wave = {"rbxassetid://507770239", Enum.AnimationPriority.Idle}}
for name, data in animationData do
animation.AnimationId = data[1]
local animationTrack = animator:LoadAnimation(animation)
animationTrack.Priority = data[2]
animationTracks[name] = animationTrack
end
animation:Destroy()
end
local animationTrack = animationTracks.Idle
animationTrack:Play(0)
local childAddedConnection: RBXScriptConnection?
local round = math.round
local function play(newAnimationTrack: AnimationTrack, fadeTime: number?)
if newAnimationTrack.IsPlaying then return end
local fadeTime = fadeTime or DEFAULT_FADE_TIME
animationTrack:Stop(fadeTime)
animationTrack = newAnimationTrack
animationTrack:Play(fadeTime)
end
local function onClimbing(speed: number)
play(animationTracks.Climb)
animationTracks.Climb:AdjustSpeed(round(speed) / 11)
end
local function onFreeFalling(active: boolean)
if active then play(animationTracks.Fall) end
end
local function onRunning(speed: number)
local speed = round(speed)
if speed > 0 then
play(animationTracks.Run)
animationTracks.Run:AdjustSpeed(speed / 16)
else
play(animationTracks.Idle)
end
end
local function onSeated(active: boolean)
if active then play(animationTracks.Sit) end
end
local function onSwimming(speed: number)
local speed = round(speed)
if speed > 2 then
play(animationTracks.Swim)
animationTracks.Swim:AdjustSpeed(speed / 12)
else
play(animationTracks.SwimIdle)
end
end
local function onChildAdded(child: Instance)
if child:IsA("Tool") and child:FindFirstChild("Handle") then
animationTracks.Tool:Play(DEFAULT_FADE_TIME)
childAddedConnection = child.ChildAdded:Connect(function(child: Instance)
if child:IsA("StringValue") and child.Name == "toolanim" then
if child.Value == "Slash" then
animationTracks.Slash:Play(0)
elseif child.Value == "Lunge" then
animationTracks.Lunge:Play(0, 1, 6)
end
child:Destroy()
end
end)
end
end
local function onChildRemoved(child: Instance)
if child:IsA("Tool") and child:FindFirstChild("Handle") then
if childAddedConnection then
childAddedConnection:Disconnect()
childAddedConnection = nil
end
animationTracks.Tool:Stop(DEFAULT_FADE_TIME)
end
end
humanoid.Climbing:Connect(onClimbing)
humanoid.FreeFalling:Connect(onFreeFalling)
humanoid.Running:Connect(onRunning)
humanoid.Seated:Connect(onSeated)
humanoid.Swimming:Connect(onSwimming)
character.ChildAdded:Connect(onChildAdded)
character.ChildRemoved:Connect(onChildRemoved)
if TextChatService.ChatVersion == Enum.ChatVersion.TextChatService
and TextChatService.CreateDefaultCommands
and TextChatService.CreateDefaultTextChannels
then
local rbxEmoteCommand = Instance.new("TextChatCommand")
rbxEmoteCommand.Name = "RBXEmoteCommand"
rbxEmoteCommand.PrimaryAlias = "/emote"
rbxEmoteCommand.SecondaryAlias = "/e"
local textChatCommands = TextChatService:WaitForChild("TextChatCommands")
textChatCommands:WaitForChild("RBXEmoteCommand"):Destroy()
rbxEmoteCommand.Parent = textChatCommands
local rbxSystem: TextChannel = TextChatService:WaitForChild("TextChannels"):WaitForChild("RBXSystem")
rbxEmoteCommand.Triggered:Connect(function(_, unfilteredText: string)
local emote = string.split(unfilteredText, " ")[2]
if emote == "cheer" then
animationTracks.Cheer:Play(DEFAULT_FADE_TIME)
elseif emote == "dance" then
play(animationTracks.Dance)
elseif emote == "dance2" then
play(animationTracks.Dance2)
elseif emote == "dance3" then
play(animationTracks.Dance3)
elseif emote == "laugh" then
animationTracks.Laugh.Looped = false
animationTracks.Laugh:Play(DEFAULT_FADE_TIME)
elseif emote == "point" then
animationTracks.Point.Looped = false
animationTracks.Point:Play(DEFAULT_FADE_TIME)
elseif emote == "wave" then
animationTracks.Wave.Looped = false
animationTracks.Wave:Play(DEFAULT_FADE_TIME)
else
rbxSystem:DisplaySystemMessage("<font color='#FF4040'>You do not own that emote.</font>")
end
end)
end
Animate 2 Version 1 Source Code
local character = script.Parent
local humanoid = character:WaitForChild"Humanoid"
local animator = humanoid:WaitForChild"Animator"
local animationTracks = {
cheer = {"rbxassetid://507770677", Enum.AnimationPriority.Idle},
Climb = {"rbxassetid://507765644", Enum.AnimationPriority.Core},
dance = {"rbxassetid://507772104", Enum.AnimationPriority.Idle},
dance2 = {"rbxassetid://507776879", Enum.AnimationPriority.Idle},
dance3 = {"rbxassetid://507777623", Enum.AnimationPriority.Idle},
Fall = {"rbxassetid://507767968", Enum.AnimationPriority.Core},
Idle = {"rbxassetid://507766388", Enum.AnimationPriority.Core},
laugh = {"rbxassetid://507770818", Enum.AnimationPriority.Idle},
Lunge = {"rbxassetid://522638767", Enum.AnimationPriority.Movement, Play = nil},
point = {"rbxassetid://507770453", Enum.AnimationPriority.Idle},
Run = {"rbxassetid://913376220", Enum.AnimationPriority.Core},
Sit = {"rbxassetid://2506281703", Enum.AnimationPriority.Core},
Slash = {"rbxassetid://522635514", Enum.AnimationPriority.Movement, Play = nil},
Swim = {"rbxassetid://913384386", Enum.AnimationPriority.Core},
SwimIdle = {"rbxassetid://913389285", Enum.AnimationPriority.Core},
Tool = {"rbxassetid://507768375", Enum.AnimationPriority.Idle, Play = nil},
wave = {"rbxassetid://507770239", Enum.AnimationPriority.Idle}}
local animation = Instance.new"Animation"
for key, value in animationTracks do
animation.AnimationId = value[1]
local animationTrack = animator:LoadAnimation(animation)
animationTrack.Priority = value[2]
animationTracks[key] = animationTrack
end
animator, animation = animation:Destroy()
local animationTrack = animationTracks.Idle
animationTrack:Play()
local childAdded
character.ChildAdded:Connect(function(instance)
if instance:IsA"Tool" and instance:FindFirstChild"Handle" then
animationTracks.Tool:Play()
childAdded = instance.ChildAdded:Connect(function(instance)
if instance:IsA"StringValue" then
local value = instance.Value
if value == "Slash" then
instance:Destroy()
animationTracks.Slash:Play(0)
elseif value == "Lunge" then
instance:Destroy()
animationTracks.Lunge:Play(0, 1, 6)
end
end
end)
end
end)
character.ChildRemoved:Connect(function(instance)
local tool = animationTracks.Tool
if tool.IsPlaying then
tool:Stop()
childAdded = childAdded:Disconnect()
end
end)
character = nil
humanoid.Climbing:Connect(function(speed)
local climb = animationTracks.Climb
if climb.IsPlaying then
climb:AdjustSpeed(speed / 5)
else
animationTrack:Stop()
animationTrack = climb
animationTrack:Play()
end
end)
humanoid.FreeFalling:Connect(function(active)
if active then
animationTrack:Stop()
animationTrack = animationTracks.Fall
animationTrack:Play()
end
end)
humanoid.Running:Connect(function(speed)
speed /= 16
if speed > 1e-2 then
local run = animationTracks.Run
if run.IsPlaying then
run:AdjustSpeed(speed)
else
animationTrack:Stop()
animationTrack = run
animationTrack:Play()
end
else
animationTrack:Stop()
animationTrack = animationTracks.Idle
animationTrack:Play()
end
end)
humanoid.Seated:Connect(function(active)
if active then
animationTrack:Stop()
animationTrack = animationTracks.Sit
animationTrack:Play()
end
end)
humanoid.Swimming:Connect(function(speed)
speed /= 10
if speed > 1 then
local swim = animationTracks.Swim
if swim.IsPlaying then
swim:AdjustSpeed(speed)
else
animationTrack:Stop()
animationTrack = swim
animationTrack:Play()
end
else
local swimIdle = animationTracks.SwimIdle
if not swimIdle.IsPlaying then
animationTrack:Stop()
animationTrack = swimIdle
animationTrack:Play()
end
end
end)
humanoid = nil
game.TextChatService:WaitForChild"TextChatCommands":WaitForChild"RBXEmoteCommand".Triggered:Connect(function(_, text)
text = string.split(text, ' ')[2]
local emotes = {
cheer = 0,
dance = 1,
dance2 = 1,
dance3 = 1,
laugh = 0,
point = 0,
wave = 0}
local emote = emotes[text]
if emote then
animationTrack:Stop()
animationTrack = animationTracks[text]
if emote == 1 then
animationTrack.Looped = true
animationTrack:Play()
else
animationTrack.Looped = false
animationTrack:Play()
animationTrack.Ended:Wait()
if animationTrack == animationTracks[text] then
animationTrack = animationTracks.Idle
animationTrack:Play()
end
end
end
end)
I think you did a great job with the script, the issue is the walking animation (under the name Run), is different from robloxâs. Here is a video of the differences. I made sure to make it so animation packs arenât used for the roblox default one.
Your script, Animate 2:
Robloxâs default Animate script:
Itâs because you can change the running animation of your avatar on roblox, I think @JohhnyLegoKing forgot about that.
You might have to get the playerâs run animation somehow and put it on the script to solve this bug.
I mean because the animate 2 running animation is the same than the default running animation.
Currently Animate 2 doesnât have a convenient way to implement per-player animations, so the animation IDs found in the animationData
table on line 16
will be used for all players within your game
If you wish to change the run animation to one used by a different package, hereâs what you need to do:
- Equip the package containing the run animation youâd like to use to your character on the Roblox website
- Go to a new empty baseplate in Studio and press play
- In the Explorer, go to: Workspace â Your character â The default Animate script â run â RunAnim
- Copy the RunAnimâs
AnimationId
and paste it in a text editor like notepad - Go back to your game that uses Animate 2 and find line
27
- Change the animation ID to the one previously pasted to the text editor
Do note that following these steps will change the run animation for every player in your game
@BrAlMeSo_Mc Iâll see if I can find a way to make Animate 2 accept different animation packages, but it will take a while to thoroughly test any changes
With the help of @InfiniteYield I managed to make a version of Animate 2 thatâs compatible with Robloxâs animation packages/bundles (although I was unable to upload it to the marketplace due to an unknown error, possibly due to the fact that this version uses InsertService)
This is what you need to do:
- Add a RemoteFunction named
AnimationIDRequest
to ReplicatedStorage - Add a server Script to ServerScriptService and inside of it write:
--!strict
local InsertService = game:GetService("InsertService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local animationIDRequest = ReplicatedStorage.AnimationIDRequest
local function onAnimationIDRequest(_, assetID: number)
if typeof(assetID) == "number" then
local success, model = pcall(InsertService.LoadAsset, InsertService, assetID)
if success then
for _, instance in model:GetDescendants() do
if instance:IsA("Animation") then return instance.AnimationId end
end
end
end
end
animationIDRequest.OnServerInvoke = onAnimationIDRequest
- Replace the code inside of my
Animate
LocalScript with:
--!strict
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TextChatService = game:GetService("TextChatService")
local DEFAULT_FADE_TIME: number = 0.1
local character: Model = script.Parent
local humanoid = character:WaitForChild("Humanoid"):: Humanoid
local animationTracks: {[string]: AnimationTrack} = {}
do
local animationIDRequest = ReplicatedStorage:WaitForChild("AnimationIDRequest")
local animator = humanoid:WaitForChild("Animator"):: Animator
local humanoidDescription = humanoid:WaitForChild("HumanoidDescription"):: HumanoidDescription
local animation = Instance.new("Animation")
local function getAnimationID(descriptionID: number, defaultID: string): string
if descriptionID == 0 then return defaultID end
local animationID = animationIDRequest:InvokeServer(descriptionID)
return if animationID then animationID else defaultID
end
local animationData: {[string]: {any}} = {
Cheer = {"rbxassetid://507770677", Enum.AnimationPriority.Idle},
Climb = {getAnimationID(humanoidDescription.ClimbAnimation, "rbxassetid://507765644"), Enum.AnimationPriority.Core},
Dance = {"rbxassetid://507772104", Enum.AnimationPriority.Core},
Dance2 = {"rbxassetid://507776879", Enum.AnimationPriority.Core},
Dance3 = {"rbxassetid://507777623", Enum.AnimationPriority.Core},
Fall = {getAnimationID(humanoidDescription.FallAnimation, "rbxassetid://507767968"), Enum.AnimationPriority.Core},
Idle = {getAnimationID(humanoidDescription.IdleAnimation, "rbxassetid://507766388"), Enum.AnimationPriority.Core},
Laugh = {"rbxassetid://507770818", Enum.AnimationPriority.Idle},
Lunge = {"rbxassetid://522638767", Enum.AnimationPriority.Movement},
Point = {"rbxassetid://507770453", Enum.AnimationPriority.Idle},
Run = {getAnimationID(humanoidDescription.RunAnimation, "rbxassetid://913376220"), Enum.AnimationPriority.Core},
Sit = {"rbxassetid://2506281703", Enum.AnimationPriority.Core},
Slash = {"rbxassetid://522635514", Enum.AnimationPriority.Movement},
Swim = {getAnimationID(humanoidDescription.SwimAnimation, "rbxassetid://913384386"), Enum.AnimationPriority.Core},
SwimIdle = {"rbxassetid://913389285", Enum.AnimationPriority.Core},
Tool = {"rbxassetid://507768375", Enum.AnimationPriority.Idle},
Wave = {"rbxassetid://507770239", Enum.AnimationPriority.Idle}}
for name, data in animationData do
animation.AnimationId = data[1]
local animationTrack = animator:LoadAnimation(animation)
animationTrack.Priority = data[2]
animationTracks[name] = animationTrack
end
animation:Destroy()
end
local animationTrack = animationTracks.Idle
animationTrack:Play(0)
local childAddedConnection: RBXScriptConnection?
local round = math.round
local function play(newAnimationTrack: AnimationTrack, fadeTime: number?)
if newAnimationTrack.IsPlaying then return end
local fadeTime = fadeTime or DEFAULT_FADE_TIME
animationTrack:Stop(fadeTime)
animationTrack = newAnimationTrack
animationTrack:Play(fadeTime)
end
local function onClimbing(speed: number)
play(animationTracks.Climb)
animationTracks.Climb:AdjustSpeed(round(speed) / 11)
end
local function onFreeFalling(active: boolean)
if active then play(animationTracks.Fall) end
end
local function onRunning(speed: number)
local speed = round(speed)
if speed > 0 then
play(animationTracks.Run)
animationTracks.Run:AdjustSpeed(speed / 16)
else
play(animationTracks.Idle)
end
end
local function onSeated(active: boolean)
if active then play(animationTracks.Sit) end
end
local function onSwimming(speed: number)
local speed = round(speed)
if speed > 2 then
play(animationTracks.Swim)
animationTracks.Swim:AdjustSpeed(speed / 12)
else
play(animationTracks.SwimIdle)
end
end
local function onChildAdded(child: Instance)
if child:IsA("Tool") and child:FindFirstChild("Handle") then
animationTracks.Tool:Play(DEFAULT_FADE_TIME)
childAddedConnection = child.ChildAdded:Connect(function(child: Instance)
if child:IsA("StringValue") and child.Name == "toolanim" then
if child.Value == "Slash" then
animationTracks.Slash:Play(0)
elseif child.Value == "Lunge" then
animationTracks.Lunge:Play(0, 1, 6)
end
child:Destroy()
end
end)
end
end
local function onChildRemoved(child: Instance)
if child:IsA("Tool") and child:FindFirstChild("Handle") then
if childAddedConnection then
childAddedConnection:Disconnect()
childAddedConnection = nil
end
animationTracks.Tool:Stop(DEFAULT_FADE_TIME)
end
end
humanoid.Climbing:Connect(onClimbing)
humanoid.FreeFalling:Connect(onFreeFalling)
humanoid.Running:Connect(onRunning)
humanoid.Seated:Connect(onSeated)
humanoid.Swimming:Connect(onSwimming)
character.ChildAdded:Connect(onChildAdded)
character.ChildRemoved:Connect(onChildRemoved)
if TextChatService.ChatVersion == Enum.ChatVersion.TextChatService
and TextChatService.CreateDefaultCommands
and TextChatService.CreateDefaultTextChannels
then
local rbxEmoteCommand = Instance.new("TextChatCommand")
rbxEmoteCommand.Name = "RBXEmoteCommand"
rbxEmoteCommand.PrimaryAlias = "/emote"
rbxEmoteCommand.SecondaryAlias = "/e"
local textChatCommands = TextChatService:WaitForChild("TextChatCommands")
textChatCommands:WaitForChild("RBXEmoteCommand"):Destroy()
rbxEmoteCommand.Parent = textChatCommands
local rbxSystem: TextChannel = TextChatService:WaitForChild("TextChannels"):WaitForChild("RBXSystem")
rbxEmoteCommand.Triggered:Connect(function(_, unfilteredText: string)
local emote = string.split(unfilteredText, " ")[2]
if emote == "cheer" then
animationTracks.Cheer:Play(DEFAULT_FADE_TIME)
elseif emote == "dance" then
play(animationTracks.Dance)
elseif emote == "dance2" then
play(animationTracks.Dance2)
elseif emote == "dance3" then
play(animationTracks.Dance3)
elseif emote == "laugh" then
animationTracks.Laugh.Looped = false
animationTracks.Laugh:Play(DEFAULT_FADE_TIME)
elseif emote == "point" then
animationTracks.Point.Looped = false
animationTracks.Point:Play(DEFAULT_FADE_TIME)
elseif emote == "wave" then
animationTracks.Wave.Looped = false
animationTracks.Wave:Play(DEFAULT_FADE_TIME)
else
rbxSystem:DisplaySystemMessage("<font color='#FF4040'>You do not own that emote.</font>")
end
end)
end
Dance2 is playing forever on othersâ view for some reason???
The issue I showcased isnt due to custom bundles. I actually just enabled the option to use robloxâs default animation pack, inside of gamesettings (shown below), I expected animation packs not to work, but I am just saying the animation in the animate 2 script for Run is different, and I think uses an older version of the script.
Though to support bundles wouldnt be much difficult and wont require the steps you did provide (which is nice though, by the way), as roblox ApplyDescription actually creates children in the current Animate script with changes to the animations of each type. Example shown in video below. Basically when roblox loads the character if the player has a custom animation bundle and the game allows it, itll place a bunch of IntValues under the Animate script, so you can update Animate 2 just to use those Animations. this will make it more complex, and is actually part of the reason Robloxâs default one is 1k lines almost, as Roblox will first check if the IntValue exists or is added and load the entire list, along with weights to essentially dictate the randomness of a certain animation playing, an example would be Idle animations, which usually have 2 or more per animation bundle, with a basic looping one, and a couple (or at least 1) Idle animation that plays occasionally, like the idle animation where the player looks around and then goes back to the looped idle animation.
Image shows Game Settings in studio with a choice for Standard Animation on player avatars. In my original reply about differences in the run animation was bc the Standard Animation option here has a different Run animation than the default inside of Animate 2.
This video I use basically an empty Animate script to showcase the fact that roblox will place a bunch of IntValues in the Animate script when a HumanoidDescription is applied, and when you change the animations via Humanoid methods to do so. I also showcase that with Standard animations selected, it actually doesnt add any of those IntValues (other than a basic Dampening one I have no clue what it does in the default script), This means the animation ids in the roblox standard Animate script are defaults and Animate 2 uses a different one for Run, as my original reply showcases.
Robloxâs default Animate script also creates children IntValues inside of itself, assumably so different scripts outside of the Animate script can modify the other animations aswell. (Such as it providing a slash animation IntValue, even though animation packs dont have a slash animation)
I was able to replicate this issue
I strongly suspect this is a bug since I can verify that Animate 2 was working correctly until the most recent update, so I will make a report as soon as possible
If anyone has more information about this, please let me know. Thank you
My intent for Animate 2 is to be a more lightweight, memory-efficient and relatively simple alternative to the default animation script, but this came at a cost of removing some of the functionality as you correctly noted
The ideal use-case for Animate 2 are games with a set animation style (as in, predetermined by the game developer), which wonât require the other capabilities the default animation script provides thus will save a bit of memory and hopefully also gain a slight performance improvement by using Animate 2
March 24th, 2024 Update
Bug fix:
- Found a solution to the problem of animations replicating incorrectly for other players
@sonicdoidao2004 Youâll enjoy this update
More details about the bug fix: