This covers the full Roblox animation pipeline, from loading tracks on Humanoid characters and AnimationController rigs to handling priority blending and replacing default character animations. The reference is thorough on the practical stuff: where to run animation code (LocalScript for players, Script for NPCs), how to wire up events like Stopped and GetMarkerReachedSignal, and runtime controls for speed, weight, and looping. The troubleshooting table at the end is genuinely useful, especially the reminder that playing player animations from a server Script breaks replication. If you're building combat systems, emotes, or custom character controllers in Roblox, this gives you the playback and blending mechanics without having to dig through the API docs.
npx -y skills add sentinelcore/roblox-skills --skill roblox-animations --agent claude-codeInstalls into .claude/skills of the current project.
| Object | Purpose |
|---|---|
Animation | Asset reference — holds AnimationId |
Animator | Lives inside Humanoid or AnimationController; loads and drives tracks |
AnimationController | Replaces Humanoid for non-character rigs |
AnimationTrack | Returned by LoadAnimation; controls playback |
| Scenario | Script Type | Location |
|---|---|---|
| Local player character | LocalScript | StarterCharacterScripts |
| NPC / server-owned model | Script | Inside model or ServerScriptService |
Never play player character animations from a
Script— they will not replicate correctly to the local client.
-- LocalScript in StarterCharacterScripts
local character = script.Parent
local animator = character:WaitForChild("Humanoid"):WaitForChild("Animator")
local animation = Instance.new("Animation")
animation.AnimationId = "rbxassetid://1234567890"
local track = animator:LoadAnimation(animation)
track:Play() -- default fade-in (0.1s), weight 1, speed 1
track:Play(0.1, 1, 0.5) -- fadeTime, weight, speed
track:AdjustSpeed(1.5) -- change speed while playing
track:AdjustWeight(0.5, 0.2) -- weight 0.5, fade over 0.2s
track:Stop() -- default fade-out (0.1s)
track:Stop(0.5) -- fade out over 0.5s
-- Fires after fade-out completes
track.Stopped:Connect(function()
print("Animation finished")
end)
-- Use :Once for one-shot cleanup
track.Stopped:Once(function()
cleanup()
end)
-- Fires when a named keyframe marker is reached
-- Marker names are set in the Roblox Animation Editor
track:GetMarkerReachedSignal("FootStep"):Connect(function(paramString)
playFootstepSound()
end)
| Property | Looped | One-Shot |
|---|---|---|
track.Looped | true | false |
| Set in | Animation Editor (loop toggle) | Animation Editor |
| Override at runtime | track.Looped = false | track.Looped = true |
| Stops automatically | No — must call track:Stop() | Yes — after one cycle |
-- Force a looped animation to play once
track.Looped = false
track:Play()
track.Stopped:Once(function() print("Done") end)
Priority controls which tracks win on contested joints. Higher priority overrides lower.
Idle < Movement < Action < Action2 < Action3 < Action4 < Core
idleTrack.Priority = Enum.AnimationPriority.Idle
runTrack.Priority = Enum.AnimationPriority.Movement
attackTrack.Priority = Enum.AnimationPriority.Action
idleTrack:Play()
runTrack:Play() -- overrides idle on shared joints
attackTrack:Play() -- blends on top for joints it owns
Weight adjusts influence when two tracks share the same priority:
trackA:Play(0, 0.6) -- weight 0.6
trackB:Play(0, 0.4) -- weight 0.4 — blended on shared joints
local animator = character:FindFirstChildOfClass("Humanoid"):FindFirstChildOfClass("Animator")
local track = animator:LoadAnimation(animation)
track:Play()
local controller = model:FindFirstChildOfClass("AnimationController")
local animator = controller:FindFirstChildOfClass("Animator")
if not animator then
animator = Instance.new("Animator")
animator.Parent = controller
end
local track = animator:LoadAnimation(animation)
track:Play()
The Animate LocalScript in the character holds animation references. Modify its AnimationId values on CharacterAdded.
-- LocalScript in StarterCharacterScripts
local animate = script.Parent:WaitForChild("Animate")
local function replaceAnim(slotName, newId)
local slot = animate:FindFirstChild(slotName)
if slot then
local animObj = slot:FindFirstChildOfClass("Animation")
if animObj then animObj.AnimationId = newId end
end
end
replaceAnim("idle", "rbxassetid://111111111")
replaceAnim("run", "rbxassetid://222222222")
replaceAnim("jump", "rbxassetid://333333333")
replaceAnim("fall", "rbxassetid://444444444")
replaceAnim("climb", "rbxassetid://555555555")
Available slots: idle, walk, run, jump, fall, climb, swim, swimidle, toolnone, toolslash, toollunge.
local function stopAll(animator, fadeTime)
for _, track in animator:GetPlayingAnimationTracks() do
track:Stop(fadeTime or 0.1)
end
end
track:Play(fadeTime, weight, speed)
-- fadeTime default 0.1 — blend-in seconds
-- weight default 1.0 — joint influence (0–1)
-- speed default 1.0 — playback rate
track.TimePosition -- current position in seconds (read/write)
track.Length -- total duration in seconds
track.IsPlaying -- bool
track.Looped -- bool (override allowed at runtime)
track.Priority -- Enum.AnimationPriority
track.WeightCurrent -- actual blended weight right now
track.WeightTarget -- target weight after fade
Priority blending affects all joints an animation touches. To play a wave only on the arms while legs animate from run/idle, the animation itself must be authored to only key upper-body bones (leave lower-body joints unkeyed in the Animation Editor). There is no runtime API to mask joints — the solution is in the animation asset, not the script.
| Mistake | Fix |
|---|---|
Playing character animations in a Script | Use LocalScript in StarterCharacterScripts |
LoadAnimation called on Humanoid (deprecated) | Call on Animator instead |
| Two animations fighting on same joints | Assign different Priority values |
Stopped fires immediately | Animation has zero length or wrong Looped setting |
GetMarkerReachedSignal never fires | Marker name misspelled, or animation not re-uploaded after adding markers |
| NPC animation not visible to other clients | Play from a Script (server), not LocalScript |
AnimationController track won't play | Missing Animator child inside AnimationController |
juliusbrussee/caveman
mattpocock/skills
shadcn/improve
obra/superpowers
forrestchang/andrej-karpathy-skills
vercel-labs/skills