Animate 2 - A more memory-efficient alternative

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

3 Likes

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 :sweat_smile:

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 :grin:


More details about the bug fix:

3 Likes

I don’t think this works anymore with Roblox’s new TextChatService. I’ve been looking everywhere for an alternative and nothing worked as good as the Animate script for emotes.

Just now, I’ve ran a test (both in Studio and using the Player), and Animate 2 worked with TextChatService without encountering any issues. Are you experiencing a bug when using Animate 2 with TextChatService enabled?


The emotes I tested were: dance, dance2, dance3, laugh, point, wave, cheer

Hey! Sorry, I should’ve added more to my reply, yes, the default emotes work, however when you try and add more custom emotes (including the normal Animate script) it no longer allows it, I created a post yesterday regarding it. If you figure out a solution it would be awesome :pray:

3 Likes

Currently doing my best to try to add custom emote support, but I’m stuck in a dead end due to this error:

Failed to load animation with sanitized ID rbxassetid://3576968026: AnimationClip loaded is not valid.

Which is odd since the animation ID is for an emote created by Roblox (and is equipped in my inventory), which should be exempt from the sanitized ID error. The default animation script doesn’t encounter this error when it loads the animations, so I’m investigating it to try to see how it avoids it

I also tried using Humanoid:PlayEmote, but it kept returning false no matter what I tried

3 Likes

It’s so frustrating!! my experience relied a lot on emotes, so I’m not sure what else to do other than create a gui with buttons to temporarily to play them.

3 Likes

I’ve just released an update cause I managed to understand how the default animation script was handling emotes (It was using the PlayEmote BindableEvent BindableFunction), and testing custom emotes in Studio seems to be working correctly for me

3 Likes

I’ve just tested it, I still seem to have the “You do not own this emote” error in the chat even though it’s a custom animation, I’ve set the id in the script, am I setting it up wrong? Sorry for bothering you.

just added the “bk” as a test

when I type “/e bk” in the chat

3 Likes

I understand the issue much better now, but unfortunately this is a situation where trying to fix this issue is creating a new problem

As I previously mentioned (and I learned today while trying to fix this issue), the default animation script uses a BindableFunction to handle emotes. The BindableFunction seems to be invoked from a core script, so I won’t be able to make any changes there. The script that’s invoking the BindableFunction passes either a string, or an animation, as an argument. It passes a string when you use one of the “regular” emotes, and an animation when you use one of the emotes you have equipped to your character

I tried handling emote command logic myself, which worked when playing the “regular” emotes, but kept giving me that error when I tried playing the equipped emotes, and the only solution I found to avoid that error is to use the BindableFunction, which unfortunately displays the “You do not own that emote” warning when you try to play a custom emote

Essentially, I don’t think there’s currently a way to handle “regular”, equipped, and custom emotes combined at the moment, without some kind of error or warning showing up


@iProjectionix

This version might allow you to use your custom emotes, and it also supports the “regular” emotes, but doesn’t support equipped emotes:

--!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 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},

		bk = {"rbxassetid://129393686778054", Enum.AnimationPriority.Idle}}

	for name, data in animationData do
		local animation = Instance.new("Animation")
		animation.AnimationId = data[1]

		local animationTrack = animator:LoadAnimation(animation)
		animationTrack.Priority = data[2]

		animationTracks[name] = animationTrack
		animation:Destroy()
	end
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]
		local animationTrack = animationTracks[emote]

		if animationTrack then
			if string.find(emote, "dance") then
				play(animationTrack)
			else
				animationTrack.Looped = false
				animationTrack:Play(DEFAULT_FADE_TIME)
			end
		else
			rbxSystem:DisplaySystemMessage("<font color='#FF4040'>You do not own that emote.</font>")
		end
	end)
end
2 Likes

Ahh you didn’t have to do that, it worked like charm, thank you ever so much!!!

3 Likes

Hey i am currently using this, and it does not seem to be working, i assume i have done something wrong, Animations are not playing at ALL

It’s quite late where I live at the moment, so I won’t be able to help you until tomorrow

It needs to be named Animate in-order to replace the default animation script, and make sure that it’s a direct child of StarterCharacterScripts, not StarterPlayerScripts or somewhere else

If you’ve made any changes to it, you can try reimporting it from the marketplace

Well, here is the issue aha, i’m respecting all the requirements in order for it to work as it should but turns out it does not. the animations are not loading at all, im just a brick walking

Are there any errors showing up in the output?

If you replaced the default animation IDs with your own, make sure that the animations were published under the same person or group that owns the game, otherwise they’ll fail to load

I also recommend temporarily disabling any other scripts which are handling the player’s animations, if you have any, as one of them could be blocking Animate 2 from running correctly

Also make sure that you don’t have any plugins running which edit the source code of scripts, as they could be accidentally breaking it

One last recommendation: If you have set Workspace’s SandboxedInstanceMode to Experimental, you’ll need to set Animate 2’s permissions in-order for it to be able to work

What’s wrong with the default one?

i think its because of that, it cant be because of custom animation ids i don’t have custom ones i just plugged and play

this looks really interesting! are you planning on adding staging animations to it?

In that case, make sure that the script’s Sandboxed property is disabled/false

If the script doesn’t have a property named Sandboxed, then SandboxedInstanceMode isn’t enabled, so the cause of the issue is something else

Does Animate 2 work if you test it in a different experience, or a new baseplate?

Another question, just to confirm: Are you using Animate 2 for player controlled characters, or for NPCs? You’ll need to copy and paste its code to a server Script with it’s RunContext set to Client if you want to use it for NPCs (also make sure to add a BindableFunction named PlayEmote as a direct child of the script, otherwise WaitForChild will yield indefinitely (or you could delete the line that’s waiting for it, if you prefer))