[v1.3.1.5] [Module] BetterAnimate alternative to Animate script

BetterAnimateShowcase


Introduction
As you know, when a player's character spawns, Roblox creates an inside character script called Animate. This script contains information on how characters's animations should work. But roblox's animate script logic and code is kinda bad (really bad).

This module is a multifunctional tool that allows you to simulate Animate script , and also has many settings that allow you to customize the logic of the script


Why should I use BetterAnimate? [❗ DOCUMENTATION ❗]

:information_source: Info:

  • I will no longer update the documentation, since it takes me more time than the development of the update itself. If you want to know what a parameter/method is responsible for, read the hints in the module.
  • I would be grateful if someone would make full documentation.

Any Rig Support

Module supports any rig, even without Humanoid, but it must have AnimationController or Humanoid


Build-in Debug
local MyAnimator = BetterAnimate.New(Character):SetDebugEnabled(true)
--or
MyAnimator:SetDebugEnabled(true)
  • Total: How much animations currently playing
  • Class: Current animation class
  • Direction: Current movedirection (custom Humanoid.MoveDirection)
  • Timer: How much time need to pass until new class animation will play
  • ID: Current Class animation ID
  • Speed: Current PrimaryPart.AssemblyLinearVelocity.Magnitude
  • State: Current Enum.HumanoidStateType
  • AnimSpeed: Current animation speed

  • Methods [Functions & Properties]

    Documentation may sometimes be incomplete as I may forget to add changes, it’s best to read the documentation inside BetterAnimate ModuleScript.

    
    --Public (I won't describe private values)
    local BetterAnimate = require(--[[PATH TO BetterAnimate]])
    
    do type([[ MAIN METHODS ]])
      --[[1]] BetterAnimate.GetMoveDirectionName(Vector3.new(0, 1, 0)) --> "UP"
    
      --[[2]] BetterAnimate.GetAnimationData(180426354) --> {ID = 180426354, Instance = Animation, Weigh = FastConfig.DefaultAnimationWeight}
    
     --[[3]] BetterAnimate.GetClassesPreset("R15") --[[> {
        Run = {{180426354}},
        Walk = {{180426354}},
        ETC....
      }
    ]]
    --[[4]]  BetterAnimate.FixCenterOfMass(PrimaryPart.CurrentPhysicalProperties, PrimaryPart)
    
    --[[5]]  BetterAnimate.LocalUtils --> Utils create by BetterAnimate
    
    --[[6]]  BetterAnimate.PresetsTag --[[> Tag for GetClassesPreset()
    ModuleWithAnimationsPresets:AddTag(BetterAnimate.PresetsTag)
    ]]
    
    end
    
    do type([[ ANIMATOR METHODS ]])
    --TODO Provide more description
    
    local MyAnimator = BetterAnimate.New(Character)
    
    --[[1]] MyAnimator:SetForcedState(Enum.HumanoidStateType.Running.Name)
    
    --[[2]] MyAnimator:SetEventEnabled("NewState") --> Disable/Enable bindable events
    
    --[[3]] MyAnimator:SetDebugEnabled(true) --> Disable/Enable debug (above character)
    
    --[[4]] MyAnimator:SetClassesPreset(ClassesPreset) --> Set pack of classes animations
    
    --[[5]] MyAnimator:SetClassesPreset("Walk", ClassPreset) --> Set class animations
    
    --[[6]] MyAnimator:SetInverseEnabled(true) --> Disable/Enable inverse animation (MoveDirection)
    
    --[[7]] MyAnimator:SetInverseDirection("Down") --> N/A
    
    --[[8]] MyAnimator:SetClassInverse("Run", true) --> N/A
    
    --[[9]] MyAnimator:SetClassTimer("Idle", NumberRange.new(6, 8)) --> N/A
    
    --[[10]] MyAnimator:SetClassMaxTimer("Idle", NumberRange.new(6, 8)) --> N/A
    
    --[[11]] MyAnimator:SetClassEmotable("Idle", true) --> N/A
    
    --[[12]] MyAnimator:SetClassAnimationSpeedAdjust("Run", 16) --> N/A
    
    --[[13]] MyAnimator:SetRunningStateRange(NumberRange.new(0.4, 9)) --> N/A
    
    --[[14]] MyAnimator:SetStateFunction(Enum.HumanoidStateType.Running.Name, function(...) ... end) --> N/A
    
    --[[15]] MyAnimator:AddAnimation("Walk", "Index", AnimationData) --> N/A
    
    --[[16]] MyAnimator:StopClassAnimation() --> Stop current Class animation
    
    --[[17]] MyAnimator:PlayClassAnimation("Wak", 0.1) --> Play Class animation
    
    --[[18]] MyAnimator:PlayToolAnimation(2959295295?) --> Play Tool animation
    
    --[[19]] MyAnimator:StopToolAnimation() --> Stop Tool animation
    
    --[[20]] MyAnimator:StopEmote() --> Stop Emote animation
    
    --[[21]] MyAnimator:PlayEmote(5252525) --> Play Emote animation
    
    --[[22]] MyAnimator:Step(task.wait(0.03), Humanoid:GetState().Name) --> Force update
    
    --[[23]] MyAnimator:Destroy() --> Destroy animator
    
    end
    
    

    Play Custom Tool Animation
    local MyAnimator = BetterAnimate.New(Character)
    MyAnimator:PlayToolAnimation(507768375) -- 507768375 for r15
    

    Fix Center Of Mass

    When equiping tool while emoting your сenter of character mass changes and let to move really fast while jumping (example).

    BetterAnimate have function to fix this:

    local PrimaryPart = Character.PrimaryPart
    local CurrentPhysicalProperties = PrimaryPart.CurrentPhysicalProperties
    
    local BetterAnimate = require(ModuleScript_BetterAnimate)
    local FixCenterOfMass = BetterAnimate.FixCenterOfMass
    
    FixCenterOfMass(CurrentPhysicalProperties, PrimaryPart)
    

    Thanks to @treebee63


    Optimization

    You can control how often the update will occur.

    local BetterAnimate = require(ModuleScript_BetterAnimate)
    local MyAnimator = BetterAnimate.New(Character)
    local Rate = 0.03
    
    MyAnimator.Trove:Add(task.defer(function()
      while true do
        MyAnimator:Step(task.wait(Rate), Character.Humanoid:GetState())
      end
    end)
    

    Inverse Animations

    BetterAnimateInverse

    To enable this you must call :SetInverseEnabled(true) method

    local BetterAnimate = require(ModuleScript_BetterAnimate)
    local MyAnimator = BetterAnimate.New(Character):SetInverseEnabled(true)
    

    Also if you want to change, when Inverse should work, use methods:

    MyAnimator:SetInverseDirection("Backward", true) --> :GetInverse() will return -1
    MyAnimator:SetClassInverse("Run", true) --> Class now support inverse
    

    Update Animations In Real Time
    local RunPreset = {
      Run1 = 507767714-- for R15
    }
    
    local WalkPreset = {
      Walk1 = 507777826 -- for R15
    }
    
    local BetterAnimate = require(ModuleScript_BetterAnimate)
    local MyAnimator = BetterAnimate.New(Character)
    
    MyAnimator:SetClassesPreset({
      Run = RunPreset,
      Walk = WalkPreset 
    })
    
    --OR
    
    MyAnimator:SetClassPreset("Walk", WalkPreset)
    
    --OR
    
    MyAnimator:AddAnimation("Walk", "Walk1", 507777826 --[[or nil if you want to remove]])
    
    

    Events [Signals]

    BetterAnimate have 4 Events:

    local BetterAnimate = require(ModuleScript_BetterAnimate)
    local MyAnimator = BetterAnimate.New(Character)
    local Events = MyAnimator.Events
    
     -- Fires on new state (Running, Falling, etc...)
    Events.NewState:Connect(function(State: string) end)
    
    -- Fires on new animation (Except tool animation)
    Events.NewAnimation:Connect(function(AnimationClass: string, AnimationName: string, AnimationTable) end)
    
    -- Fires on new Vector3 MoveDirection
    Events.NewMoveDirection:Connect(function(MoveDirection: Vector3, MoveDirectionName: string) end)
    
    -- Fires on animation keyframe reached (alternative to GetMarkerReachedSignal) (Including tool animation)
    Events.KeyframeReached:Connect(function(Keyframe: string) end)
    
    

    Chaining

    BetterAnimate supports “Set” chain methods:

    local BetterAnimate = require(ModuleScript_BetterAnimate)
    local MyAnimator = BetterAnimate.New(Character)
      :SetClassEmotable("Idle", true)
      :SetRunningStateRange(Vector2.new(4, 9))
      :SetInverseEnabled(true)
    

    Play Emote
    local BetterAnimate = require(ModuleScript_BetterAnimate)
    local MyAnimator = BetterAnimate.New(Character)
    
    local DirectionEvent = MyAnimator.Events["NewMoveDirection"]
    
    DirectionEvent:Connect(function(MoveDirection: Vector3, MoveDirectionName: string)
    	if MoveDirection.Magnitude > 1 --[[you can change to 0 if you want]] then
    		MyAnimator:StopEmote()
    	end
    end)
    
    MyAnimator:PlayEmote(MyAnimator._RigType == `R6` and 182436842 or 15609995579)
    
    

    To customize when Character can emote, use :SetClassEmotable(“Idle”, true)

    MyAnimator:SetClassEmotable("Idle", true)
    

    FastConfig

    List of values, that can be changed at any time manually


    * R6ClimbFix --> Fix r6 climbing speed
    * EmoteIngnoreEmotable --> [DEPRECATED]
    * AnimationSpeedMultiplier --> `AnimationTrack:AdjustSpeed(Speed * FastConfig.AnimationSpeedMultiplier)`
    * AnimationPlayTransition --> `AnimationTrack:Play(FastConfig.AnimationPlayTransition)`
    * AnimationStopTransition --> `AnimationTrack:Stop(FastConfig.AnimationStopTransition)`
    * ToolAnimationPlayTransition --> `AnimationTrack:Play(FastConfig.ToolAnimationPlayTransition)`
    * ToolAnimationStopTransition --> `AnimationTrack:Stop(FastConfig.ToolAnimationStopTransition)`
    * WaitFallOnJump --> Wait until play Fall animation on jump
    * DefaultAnimationLength --> If Animation.Length == 0 it will use `FastConfig.DefaultAnimationLength`
    * DefaultAnimationWeight --> Chance of animation
    * AnimationPriority --> `AnimationTrack.Priority = FastConfig.AnimationPriority`
    * ToolAnimationPriority --> `AnimationTrack.Priority = FastConfig.ToolAnimationPriority`
    * MoveDirection --> Set your own MoveDirection (Don't forget to update it every `:Step()`)
    * SetAnimationOnIdDifference --> New animation (Instance) will play if `Animation.AnimationId` is not not equal to the old one

    Example:

    local BetterAnimate = require(ModuleScript_BetterAnimate)
    local MyAnimator = BetterAnimate.New(Character)
    
    local FastConfig = MyAnimator.FastConfig
    FastConfig.R6ClimbFix = true
    FastConfig.AnimationSpeedMultiplier = 2
    FastConfig.DefaultAnimationWeight = 15
    

    How to setup
    local Service_Players = game:GetService(`Players`)
    local Service_ReplicatedStorage = game:GetService(`ReplicatedStorage`)
    
    local ModuleScript_BetterAnimate = Service_ReplicatedStorage:WaitForChild(`BetterAnimate`)
    local BetterAnimate = require(ModuleScript_BetterAnimate)
    
    local LocalPlayer = Service_Players.LocalPlayer
    local Character = LocalPlayer.Character or LocalPlayer.CharacterAdded:Wait()
    local Humanoid = Character:FindFirstChildWhichIsA(`Humanoid`) :: Humanoid
    local PrimaryPart = Character.PrimaryPart or Character:GetPropertyChangedSignal(`PrimaryPart`):Wait()-- and Character.PrimaryPart
    PrimaryPart = Character.PrimaryPart
    
    local _PhysicalProperties = PrimaryPart.CurrentPhysicalProperties
    local ClassesPreset = BetterAnimate.GetClassesPreset(Humanoid.RigType.Name)
    
    local MyAnimator = BetterAnimate.New(Character)
    	:SetInverseEnabled(true)
    	:SetClassesPreset(ClassesPreset)
    	:SetDebugEnabled(true)
    
    MyAnimator.FastConfig.R6ClimbFix = true
    
    do type([[ Events (Signals) ]])
    	MyAnimator.Events.MarkerReached:Connect(function(Keyframe: string)
    		--print(`Keyframe reached {Keyframe}`)
    	end)
    	
    	MyAnimator.Events.NewMoveDirection:Connect(function(MoveDirection: Vector3, MoveDirectionName: BetterAnimate.BetterAnimate_Directions)
    		if MoveDirection.Magnitude > 0 then
    			MyAnimator:StopEmote()
    		end
    	end)
    	
    	MyAnimator.Events.NewAnimation:Connect(function(Class: string, Index: any, AnimationData: BetterAnimate.BetterAnimate_AnimationData)
    		--print(`New animation {Index}`)
    	end)
    	
    	MyAnimator.Events.NewState:Connect(function(State: string)
    		--print(`New state {State}`)
    	end)
    end
    
    do type([[ Tool ]])
    	MyAnimator.Trove:Add(Character.ChildAdded:Connect(function(Descendant)
    		if Descendant:IsA(`Tool`) then
    			MyAnimator:PlayToolAnimation()
    		end
    
    		BetterAnimate.FixCenterOfMass(_PhysicalProperties, PrimaryPart)
    	end))
    
    	MyAnimator.Trove:Add(Character.ChildRemoved:Connect(function(Descendant)
    		if Descendant:IsA(`Tool`) then
    			MyAnimator:StopToolAnimation()
    		end
    
    		BetterAnimate.FixCenterOfMass(_PhysicalProperties, PrimaryPart)
    	end))
    end
    
    
    do type([[ Personal tests ]]) -- Some test
    	
    	do
    		MyAnimator.Trove:Add(script:GetAttributeChangedSignal(`Debug`):Connect(function()
    			MyAnimator:SetDebugEnabled(script:GetAttribute(`Debug`))
    		end))
    	end
    	
    	do
    		MyAnimator.Trove:Add(script:GetAttributeChangedSignal(`Emote`):Connect(function()
    			if script:GetAttribute(`Emote`) then
    				MyAnimator:PlayEmote(MyAnimator._RigType == `R6` and 182436842 or 15609995579)
    			else
    				MyAnimator:StopEmote()
    			end
    		end))
    	end
    	
    	do type([[ CreateAnimationManager example ]])
    		
    		local AnimationManager
    		
    		MyAnimator.Trove:Add(script:GetAttributeChangedSignal(`CreateAnimationManager`):Connect(function()
    			if script:GetAttribute(`CreateAnimationManager`) then
    				AnimationManager = MyAnimator:CreateAnimationManager(10714338461)
    				AnimationManager:SetPrority(Enum.AnimationPriority.Action4)
    				AnimationManager:SetOnDidLoop(function()
    					print(`Looped`)
    				end)
    				
    				AnimationManager:SetOnEnded(function()
    					print(`Ended`)
    				end)
    				AnimationManager:Play()
    			else
    				if AnimationManager then
    					AnimationManager:Destroy()
    				end
    			end
    		end))
    	end
    end
    
    do type([[ Animations logic ]])
    	Humanoid.Died:Once(function()
    		MyAnimator:Destroy()
    	end)
    	
    	local NextState = nil
    	
    	MyAnimator.Trove:Add(Humanoid.Jumping:Connect(function() -- Deffering, since we don't use Humanoid.StateChanged
    		MyAnimator:SetForcedState(Enum.HumanoidStateType.Jumping.Name)
    	end))
    	
    	MyAnimator.Trove:Add(task.defer(function()
    		while true do
    			MyAnimator.FastConfig.MoveDirection = PrimaryPart.CFrame:VectorToObjectSpace(Humanoid.MoveDirection)
    			MyAnimator:Step(task.wait(), Humanoid:GetState().Name)
    		end
    	end))
    end
    
    do type([[ Roblox buildin PlayEmote ]])
    	local PlayEmote = script:FindFirstChild(`PlayEmote`)
    	if PlayEmote and PlayEmote:IsA(`BindableFunction`) then
    	
    		PlayEmote.OnInvoke = function(Animation: string | Animation) --[You must to return true]]
    			
    			if typeof(Animation) == `Instance` then
    				--print(Animation.AnimationId)
    				return true, MyAnimator:PlayEmote(Animation)
    			else -- string
    				--Im bored, write your own method on how to get these animations
    			end
    		end
    	end
    end
    

    Icon Link Note
    ModuleScript Module Get latest version
    Playground Playground Test BetterAnimate
    Github Open source code

    :speech_balloon: Feel free to give feedback :speech_balloon:

    118 Likes

    I kinda like what you done here. Might be useful for people who are looking to have a more simple animated script. But my tip is to try and make this work for all avatar sizes as Roblox animated script works on all avatars.

    5 Likes

    My version supports this too, simply it is a modified version of the script from Roblox

    5 Likes

    Could you please try to improve the R6 Animate script while you’re at it?
    That would be nice for my game.

    1 Like

    Only If I have free time, reading scripts from Roblox is a pain (Now supports any rig)

    3 Likes

    Can we have an animation playground game? I’m usually on Mobile and sometimes I don’t have the time to check things on my laptop. Thanks.

    1 Like

    I’ve been looking for something to specifically fix animations not updating in real-time when they’re changed at runtime.

    I’d use this but I noticed things like the point / wave animations not playing when running the chat commands.

    I’d love to use this if it was fully complete, hoping the new version fixes some of these things.

    1 Like

    [DEPRECATED]

    Update 1.1.4

    Created place for Better Animate (Uncopylocked), where you can test it
    BetterAnimate - Roblox

    Fixed chat commands not working

    Small fixes and code cleaning

    Code Optimization

    Before:


    After:


    5 Likes

    Does this script replicate the default animations that are overwritten? When using the default animation script, it’s inconsistently replicated.

    Do you mean playing 2 animations at once (For example: old walk and new updated walk in real time)?

    Answer: No.

    This is impressive!
    I like the playing animation backwards feature and the change animations in real time.

    There are some Suggestions and Bugs I have for you.

    Bugs:
    1. When jumping while moving backwards, Animation might play backwards and trigger the fall animation too early which messes up the look of it.
    2. If the humanoid is swimming backwards it bugs the animation out and loops it (Depends on animation)
    3. There are a lot of type checked code bugs that are present if “–!strict” is present
    Suggestions
    1. Make a settings module to enable and disable extra features
    2. Use the base of the animate script I will provide for cleanliness and such
    3. Set animations on client and/or server for the benefits that come with it

    Animate Script:
    CustomAnimate.rbxm (8.6 KB)

    Essentially, all it is is just a Animate script that relies on modules and a controller to be more clean and efficient.

    3 Likes

    [DEPRECATED]

    Update 1.1.4.1

    Fixed reversed animations
    Removed --!strict
    Fixed emote animations randomly can stop playing
    Added settings sections (Image below)
    Removed folder creating when changing animation in real time

    3 Likes

    Hello, thanks for bug reports!
    About your suggestions, its a good idea, but i currently have no plans for it.
    I have an idea in my head about how this could work, and it’s a lot of work for me.

    The example you showed is not very good in that all these modules are replicated when the character respawns

    2 Likes

    Your right, but the general idea is just more organization and whatnot.

    Good update though! I like it

    2 Likes

    After a while, I realized that I was doing some kind of garbage, I already have an idea in my head on how to simplify and improve everything. It will also work as a module (no longer 2 different scripts: server & client)

    But! My module will use another module, for simplicity: Trove

    New update coming soon!

    5 Likes

    Global update v1.2 is out!

    Everything is changed. I hope you like update, as I tried to make everything more optimized and improved.

    Also, if you have ideas on how to improve BetterAnimate, lmk in private messages

    2 Likes

    This resource has been VASTLY improved however, this should be considered for the default animate script…

    I like your usage of Trove, the modularity you’ve crafted and the customization…
    Thank you! This script is such a big improvement.

    inverse walk doesn’t work with inverse=true (im using r15 character)

    [DEPRECATED]

    Hello, you need to use Enabled_Inverse not Inverse

    Here’s the code how it should look like:

    local BetterAnimate = require(PATH_TO_MODULE)
    local MyAnimator = BetterAnimate.New(Character, {
    	Enabled_Inverse = true,
    })
    
    MyAnimator:Start()
    
    1 Like