Roblox Animation

Animation in Roblox: from the empty rig to the script that makes it play in the game

Animating in Roblox is two things in two places: drawing the movement in the Animation Editor and telling it to play from a script. This guide walks both, with the Add Keyframe Here detail that ruins timelines and the case of the rig with no Humanoid.

by Gustavo Cantino9 min readBeginner

Produced with AI from the sources cited below, and checked by automated verification against those same sources. No person read this text before publication. Editorial Policy

Typographic plate with the Roblox Animation category in large type, the beginner level below it, and a field of vertical lime green bars on a dark background.

You drew the animation in Studio, it plays beautifully in the editor, and in the published game the character stands still. That is the point at which most people give up on animation in Roblox — and the problem is almost never the animation.

Animating in Roblox is two separate things, done in two different places: drawing the movement in the Animation Editor, and telling the movement to play from a script. The official documentation covers them on different pages, and it is exactly that border that swallows beginners. This guide walks the whole route: from the empty rig to the script that makes the animation happen on the server.

The problem

The first obstacle turns up before any animation: you can only create an animation on top of a rig. That is not an editor preference, it is the entry condition — the documentation says you can only create animations using rigs, and a loose Part in the workspace will not do.

A rig, in the official definition, is an object with individual sections joined by joints. Anyone without one in place can insert a ready-made one with the Rig Generator, which comes with the parts and the basic mechanisms of a character. That clears the first blocker in thirty seconds, and it is where it is worth starting even if the end goal is animating a door.

The second obstacle is conceptual and costs more. The Animation Editor is for drawing and publishing your own animations on rigs — publishing, here, meaning turning them into an asset with an id. It does not wire the animation into the game. Once created, the animation needs a script to take the field: the documentation is explicit in saying you have to use scripts to include the animation in your game. Anyone waiting for the character to start moving just because the animation exists is waiting for something Roblox never promised.

And there is a third thing, which only bites later: the timeline has a unit of its own. The animation runs at 30 frames per second, and the ruler shows seconds and frames — 0:15 is half a second, not fifteen seconds. The window opens showing a duration of one second, which is short for almost everything; widening the timeline is the first thing you do, not the last.

Step by step

1. Put a rig in the workspace. If you do not have one, use the Rig Generator. If you do, select it in the viewport or the Explorer before opening the editor — with nothing selected, the editor has nobody to work on.

2. Recognise the three areas of the window. The editor splits into playback controls, a track list and the timeline. The position indicator shows where the scrubber is, in seconds and frames. It is worth spending a minute here: almost every timeline mistake comes from not knowing which of those three areas is being clicked.

3. Create the poses. An animation is made of poses — and a pose, in the official definition, is the position and orientation of Bone or MeshPart objects inside the rig. In practice: expand the rig in the Explorer, select the bone or the mesh (hand, foot, torso), take the scrubber to the frame where the pose should exist, and move or rotate.

One detail that saves a lot of time: you can switch between move and rotate by pressing R. Both modes work as they do elsewhere in Studio, with the same snap settings from the toolbar.

With each adjustment, a new track appears in the list and a new keyframe on the timeline, on that bone and at that frame. That is the sign the pose was recorded — if the track did not appear, the movement did not go in.

4. Watch it run. Before refining anything, press the space bar. With two poses — say, the head looking forward and then turned to the side — the editor already interpolates the middle of the route on its own.

5. Refine the keyframes. This is where the animation stops looking like a draft: adjusting keyframes one by one improves the final result considerably. The operations that matter are four: insert (context menu, Add Keyframe Here), move (drag to another time position), duplicate (copy and paste with the scrubber at the destination) and delete (select and press Delete or Backspace). A selected keyframe gets a blue border — that border is the confirmation that the next operation will land in the right place.

6. Choose the easing. By default the piece goes from one keyframe to the next at a uniform speed, linear easing. It is what makes a beginner's animation look like a robot. The documentation lists five styles, and it is worth knowing what each does before you start testing:

  • Linear — constant speed.
  • Constant — removes the interpolation between the selected keyframe and the next one; the animation jumps from pose to pose.
  • CubicV2 — eases in and out with cubic interpolation. It is what the documentation itself uses to show natural movement.
  • Elastic — moves as if the object were attached to a rubber band.
  • Bounce — moves as if the start or the end were bouncing.

The easing direction is the other half: it defines which end of the movement gets the effect. By default, the movement leaves slower at the start and faster at the end. Changing the direction is the adjustment that fixes a movement that "arrives wrong" without anyone being able to say why.

7. Publish and take the id. The animation becomes an asset, and it is its id that the script will use.

8. Write the script. The official pattern for a character has four steps: make sure the Humanoid has an Animator, create an Animation instance with the right AnimationId, load it through Animator:LoadAnimation() — which returns an AnimationTrack — and play it with the track's Play().

For a rig that does not have a Humanoid — door, lever, creature, vehicle — the route changes: you have to create an AnimationController with an Animator child. Without that there is nowhere to load the track, and the script fails silently.

The artifact

The module below solves both cases in the same place and caches the loaded track instead of reloading it on every use. It also drops the cache when the rig leaves the game — a discreet leak that only shows up after a few hours of a full server.

Swap the id for your own animation's. The GetMarkerReachedSignal at the end only fires if the animation has a marker with that name: it exists to detect when a named animation event happens, and it is how you sync sound, damage or an effect to the exact frame of the movement.

Common mistakes

The mistake that ruins timelines most is in a line of the documentation almost nobody reads: the keyframe goes in at the time position nearest to where you clicked, not where the scrubber is.

The head of the person animating works the other way round. You carefully take the scrubber to 0:20, right-click somewhere on the track to open the menu, choose Add Keyframe Here — and the keyframe is born where the cursor was, not at 0:20. The next pose goes in misaligned, the movement comes out crooked, and since the difference is usually a few frames, nobody suspects the click: they suspect their own eyes.

The fix is tedious and infallible: right-click exactly over the time position where the keyframe should be born. And check the ruler before moving on — fixing one out-of-place keyframe is a drag; fixing ten is starting over.

The second common mistake is the rig with no Humanoid. A script copied from a character tutorial looks for the Humanoid, does not find it, and either hangs on WaitForChild or sails straight past without playing anything. There is no error message saying "this rig is not a character" — the animation simply does not happen.

Next step

Once your own movement is working, the next step is deciding whether it replaces something rather than adding to it. The default Roblox character already comes with running, climbing, swimming and jumping animations, and the documentation describes the two routes side by side: playing an animation manually from a script, or replacing the character's default animations.

The swap is simpler than it looks and does not require animating anything: you can take an id from the catalogue. The documentation itself gives the example of swapping the default run for the Ninja Run variant, with id 656118852. The script that makes that swap goes in ServerScriptService, and it starts by stopping the tracks already playing before rewriting the Animate references.

It is a good first exercise precisely because there is nothing authorial about it: if your game's run changes, the plumbing is standing, and then it is worth spending the editor hours on the movement only your game has.

Sources

How we correct mistakes

Ready-to-use artifact

Playing an animation on any rig, with or without a Humanoid (ServerScriptService)
lua
-- ServerScriptService: a small module that solves BOTH cases from the
-- documentation — a rig with a Humanoid (character) and a rig without one
-- (door, creature, lever) — and keeps the loaded track instead of
-- loading it again on every use.
--
-- Swap the id below for YOUR published animation.

local Animations = {}

local KICK_ID = "rbxassetid://2515090838"

-- cache: rig -> { [animationId] = AnimationTrack }
local tracks = {}

-- The Animator is what loads the track. On a character it lives in the Humanoid.
-- On a rig with no Humanoid the documentation says to create an AnimationController
-- with an Animator child — without that, LoadAnimation has nowhere to happen.
local function animatorOf(rig)
	local humanoid = rig:FindFirstChildOfClass("Humanoid")
	if humanoid then
		return humanoid:WaitForChild("Animator", 5)
	end

	local controller = rig:FindFirstChildOfClass("AnimationController")
	if not controller then
		controller = Instance.new("AnimationController")
		controller.Parent = rig
	end

	local animator = controller:FindFirstChildOfClass("Animator")
	if not animator then
		animator = Instance.new("Animator")
		animator.Parent = controller
	end
	return animator
end

-- Returns the track ready to play. Loads once per rig.
function Animations.track(rig, animationId)
	local animator = animatorOf(rig)
	if not animator then
		warn("no Animator in " .. rig:GetFullName() .. ": the animation will not play")
		return nil
	end

	tracks[rig] = tracks[rig] or {}
	if tracks[rig][animationId] then
		return tracks[rig][animationId]
	end

	local animation = Instance.new("Animation")
	animation.AnimationId = animationId

	local track = animator:LoadAnimation(animation)
	tracks[rig][animationId] = track

	-- a rig removed from the game takes the cache with it, otherwise this leaks
	rig.AncestryChanged:Connect(function(_, parent)
		if not parent then
			tracks[rig] = nil
		end
	end)

	return track
end

function Animations.play(rig, animationId)
	local track = Animations.track(rig, animationId)
	if track then
		track:Play()
	end
	return track
end

-- Example: play on the character of whoever joins, and listen for the marker.
local Players = game:GetService("Players")

Players.PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(function(character)
		character:WaitForChild("Humanoid")

		local track = Animations.track(character, KICK_ID)
		if not track then
			return
		end

		-- only fires if the animation has a marker with this name
		track:GetMarkerReachedSignal("KickEnd"):Connect(function(parameter)
			print(player.Name .. " finished the kick", parameter)
		end)
	end)
end)

return Animations
In this article

Keep reading