
Roblox Animation
- 55 installs
- 11 repo stars
- Updated August 3, 2026
- nonlooped/roblox-suite
Guides modern Roblox animation and tweening with Animator, IKControl, markers, and TweenService for character motion and animated UI.
About
Provides current Roblox animation and tweening knowledge covering Animator/AnimationTrack, IKControl, marker events, and TweenService for UI and 3D. A developer uses it for character locomotion, emotes, object motion, and animated UI in Roblox.
- Uses modern Animator/AnimationTrack over deprecated Humanoid:LoadAnimation and IKControl for posing
- Covers TweenService for UI and 3D property tweens, priorities, caching, and marker-driven events
Roblox Animation by the numbers
- 55 all-time installs (skills.sh)
- +15 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #155 of 247 Game Development skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/nonlooped/roblox-suite --skill roblox-animationAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 55 |
|---|---|
| repo stars | ★ 11 |
| Last updated | August 3, 2026 |
| Repository | nonlooped/roblox-suite ↗ |
What it does
Guides modern Roblox animation and tweening with Animator, IKControl, markers, and TweenService for character motion and animated UI.
Files
roblox-animation
This skill provides comprehensive, current knowledge of Roblox's two primary motion systems and how they interact with UI, effects, and gameplay. Many models still recommend deprecated APIs or give shallow "use TweenService" advice without the nuances of priorities, caching, IK constraints, marker-driven gameplay, proper UI scale/AnchorPoint usage, or performance tradeoffs.
Key official sources:
- https://create.roblox.com/docs/animation (overview)
- https://create.roblox.com/docs/animation/editor
- https://create.roblox.com/docs/animation/inverse-kinematics
- https://create.roblox.com/docs/animation/events
- https://create.roblox.com/docs/ui/animation (UI tweens + typewriter)
- Engine: Animator, AnimationTrack, Animation, AnimationClipProvider, IKControl, TweenService, TweenInfo, Tween, Enum.AnimationPriority, Enum.EasingStyle, etc.
- Full reference: https://create.roblox.com/docs/reference/engine
Structure of this skill:
- This SKILL.md gives decision frameworks, recommended workflows, integration patterns, and explicit pointers to
references/files for deep dives. references/contains granular, long-form technical references for each major sub-area.scripts/can hold custom loaders, tween wrappers, or IK setup utilities you create for your project.
Cross-skill usage:
- Combine with roblox-user-interfaces for UI motion details and "particles in UI".
- Combine with roblox-vfx when animations should trigger emitters, beams, or trails via markers.
- Combine with roblox-core for Animator acquisition, RunService timing, and preloading via ContentProvider.
- Use roblox-networking to decide where to play/ control animations (server replication vs client-only cosmetics).
When to use this skill
- Any character or rig movement (walk, jump, attack, emote, interact, procedural head tracking, foot planting).
- Smooth UI feedback (button hover/click scales, menu slide-ins, health bar fills, countdowns, typewriter dialogue).
- Object/property interpolation in 3D (doors, elevators, camera paths, color shifts on lights/parts).
- Event-driven gameplay tied to animation timing (footstep sounds/particles, attack hit windows, ability VFX).
- Debugging choppy/stiff animations, priority conflicts, IK unnatural bending, or UI that "pops" instead of eases.
High-level decision framework
3D rig/character motion that needs to look authored and blendable? → Animation system (Animator + AnimationTrack). Pre-authored in Editor or from catalog, played with priority/weight/fade/speed. Drive gameplay from markers.
Simple property changes, UI transitions, or one-off object motion? → TweenService. Cheaper, easier, perfect for GuiObjects (scale + AnchorPoint + UDim2), CFrame, Color3, Transparency, NumberSequence, etc.
Need procedural interaction with environment (hand reaching, head tracking, foot placement on uneven ground)? → IKControl (procedural) + optional AnimationTracks or constraints for limits. Often combined with animation events.
Both? Common and powerful: Play a locomotion track on low priority while using IKControl or tweens for upper-body or UI overlays. Use markers in the track to start/stop IK or fire tweens/effects.
See references/3d-animations.md and references/ui-tweens-and-sequences.md for details.
3D Animation Workflow (modern, recommended)
1. Rig preparation — Use Rig Builder or properly skinned/boned custom models. Ensure the model has a Humanoid or AnimationController, and that an Animator exists as a child (create one if it is missing). 2. Authoring — Animation Editor (Window → Animation Editor). Create poses by manipulating bones/meshes, set keyframes, choose per-keyframe easing style + direction (Linear, CubicV2, Elastic, Bounce, Constant; In/Out/InOut). Optimize keyframes when the timeline gets noisy. 3. Events/Markers — Show Animation Events track. Add named markers (with optional parameter string). These are the cleanest way to synchronize gameplay (sounds, particles, hitboxes, VFX) to animation without polling TimePosition. 4. Priorities — Set via editor or at runtime on the track. Core < Idle < Movement < Action < Action2/3/4. Higher priority wins blending. 5. Looping & export — Enable looping in editor (duplicate first keyframe to end for seamless loop if needed). For default replacement animations, name the final keyframe exactly "End" (case-sensitive). Publish to Roblox (gives asset ID). Save locally to ServerStorage during iteration. 6. Runtime loading & playback (modern API):
local ContentProvider = game:GetService("ContentProvider")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- Player characters already have an Animator created by the engine on the server.
-- Custom rigs / NPCs may need fallback creation.
local animator = humanoid:FindFirstChildOfClass("Animator")
or humanoid:WaitForChild("Animator", 1)
if not animator then
animator = Instance.new("Animator")
animator.Parent = humanoid
end
-- Cache the Animation instance; Animator:LoadAnimation caches the returned track
-- when called with the same Animation object on the same Animator.
local anim = ReplicatedStorage:FindFirstChild("MyAnimation") or Instance.new("Animation")
if not anim.AnimationId then
anim.AnimationId = "rbxassetid://YOUR_ID"
anim.Name = "MyAnimation"
anim.Parent = ReplicatedStorage
end
-- Preload asset instances, not AnimationTracks.
ContentProvider:PreloadAsync({anim})
local track = animator:LoadAnimation(anim)
track.Priority = Enum.AnimationPriority.Action
track:Play(fadeTime, weight, speed)
-- Store and disconnect connections when the rig/GUI is destroyed.
local markerConn = track:GetMarkerReachedSignal("FootStep"):Connect(function(param)
-- spawn dust, play sound, etc. Param can be parsed.
task.defer(function() ... end) -- defer gameplay side-effects
end)
local stoppedConn = track.Stopped:Connect(function() end) -- cleanup / state tracking
local endedConn = track.Ended:Connect(function() end) -- animation has finished moving the rigWarning: KeyframeReached is the older event; prefer GetMarkerReachedSignal for all new work. 7. Ownership & replication — For a client-played animation on the player's own character to replicate to the server, the animation asset must be owned by the player or by the experience. Group/creator-owned experiences must own the asset. This is separate from the Animator's authority. 8. Track caching & cleanup — Animator:LoadAnimation returns the same track if called again with the same Animation instance on the same Animator. Stop tracks on Humanoid.Died / Player.CharacterRemoving and disconnect event connections.
IK for procedural enhancement (covered in references/3d-animations.md):
- Add IKControl under Humanoid or AnimationController.
- Required: Type (Position/Transform/Rotation/LookAt/etc.), EndEffector (hand/foot bone), Target (Attachment, part, or world position object), ChainRoot.
- Tune P and SmoothTime for responsiveness vs. stability.
- Add Constraints (Hinge for elbows/knees, BallSocket with LimitsEnabled + UpperAngle for wrists) to keep joints natural. Constraint attachments should be placed at the same joint locations as the Motor6D C0/C1 offsets.
- Test live in Play mode — you can edit IKControls at runtime.
- Often driven or enabled/disabled from animation markers.
Curve animations vs KeyframeSequence — Newer system supports per-channel curves for finer control (promote from keyframe animation in editor).
See references/3d-animations.md for full editor details, priorities, loading patterns, IK constraints, and event examples.
UI Animation & TweenService (the workhorse for "Animations in the UI")
TweenService:Create(instance, TweenInfo, propertyTable) → Tween
TweenInfo.new(duration, easingStyle, easingDirection, repeatCount, reverses, delayTime)
EasingStyles: Linear, Sine, Quad (good default), Cubic, Quart, Quint, Exponential, Circular, Back (overshoot), Bounce, Elastic. Directions: In, Out (default for many), InOut.
Always design UI with scale + AnchorPoint:
object.AnchorPoint = Vector2.new(0.5, 0.5)- Target Position = UDim2.fromScale(0.5, 0.5) for screen center.
- Add UIAspectRatioConstraint when tweening Size to preserve intended proportions.
Common tweenable UI properties (single or multi-property):
- Position, Size, Rotation
- BackgroundTransparency, BackgroundColor3
- ImageTransparency, ImageColor3 (for ImageLabel/Button)
- TextTransparency, TextColor3 (legacy TextStrokeTransparency/TextStrokeColor3 still tweenable but discouraged; add a UIStroke child instead)
- UIStroke.Thickness, .Color, .Transparency
- CanvasGroup.GroupTransparency and GroupColor3 (best way to fade or recolor whole panels at once)
- ScrollingFrame.CanvasPosition (for programmatic scroll)
Sequences & chaining:
local t1 = TweenService:Create(obj, info, {Size = UDim2.fromScale(1.2, 1.2)})
local t2 = TweenService:Create(obj, info, {Size = UDim2.fromScale(1, 1)})
local conn
t1:Play()
conn = t1.Completed:Connect(function()
conn:Disconnect()
t2:Play()
end)
-- Cleanup tweens/connections when the GUI is destroyed:
obj.AncestryChanged:Connect(function(_, parent)
if not parent then
if conn then conn:Disconnect() end
t1:Destroy()
t2:Destroy()
end
end)Or use a small state machine / table of tweens.
Typewriter / animated text reveal (very common): See the full reusable module in the official ui/animation.md page. It uses utf8.codes/utf8.codepoint (or a grapheme-splitting library for user-perceived characters) + TextLabel.MaxVisibleGraphemes + optional LocalizationService translator. Extremely useful for dialogue, tutorials, lore.
Style transitions (beta): Via Style Editor + StyleRule definitions for more CSS-like declarative motion.
Performance notes:
- Tweening many UI elements or very large transparent areas costs fill-rate.
- Prefer CanvasGroup for group fades/colors.
- Cancel tweens you no longer need (
tween:Cancel()). - For 3D objects you can also tween CFrame/Size/Color/Transparency, but authored animation tracks + constraints are usually better for complex rigs.
See references/ui-tweens-and-sequences.md for exhaustive single-property examples, multi-property, easing graphs guidance, typewriter implementation notes, and gotchas.
Integration Patterns & Polish
- Animation markers → everything else: Footstep marker → play sound + emit particle at foot Attachment. Attack marker → enable hitbox or IK reach + spawn muzzle flash. "AbilityStart" marker → start a Tween on a UI cooldown ring or BillboardGui.
- Priorities + weight for layering: Idle (low) + Walk (medium) + Action (high, weight 1.0 with fade). Use AdjustWeight and AdjustSpeed at runtime.
- Client vs Server playback: For a player's own character, animations played on the client replicate to the server via the Animator (subject to ownership/permissions); for NPCs and other characters, the server is the authority. Cosmetic or prediction-friendly animations can be client-only. Gameplay-affecting timing (damage windows, movement locks) should be validated server-side; do not treat client markers as authoritative proof of a hit.
- Preload + cache tracks: Load once per rig type, reuse the AnimationTrack objects. Use
AnimationClipProviderfor async animation loading when you need previews or streaming behavior. - UI + 3D harmony: Tween a 3D part or Attachment while a BillboardGui or SurfaceGui on it also tweens (or uses ViewportFrame for embedded 3D previews with parts/meshes/cameras — note that ParticleEmitters/Beams/Trails/Lights do not render inside ViewportFrame).
- Testing: Different devices have different frame rates and input latency. Test easing feels on mobile + desktop. Use MicroProfiler for heavy simultaneous tweens.
Common Outdated / Harmful Patterns This Skill Eliminates
Humanoid:LoadAnimation(anim)(deprecated — use Animator).- Polling
track.TimePositionevery frame instead of markers. - Tweening raw pixel offsets instead of scale + AnchorPoint (breaks on resolution/aspect changes).
- Playing high-priority actions without fade time (jarring).
- Never preloading (first play hitch).
- Using the same low AnimationPriority for everything (idles fighting actions).
- Ignoring IK constraints (elbows/knees bending backwards, wrists at impossible angles).
- Tweening dozens of individual UI elements instead of using CanvasGroup or layouts.
How to use the references/ and scripts/
When implementing:
- Read references/3d-animations.md for rig/Animator/Track/IK/event details and full code patterns.
- Read references/ui-tweens-and-sequences.md for every common UI property tween + sequences + typewriter.
- Read references/integration-and-events.md for marker-driven VFX, cross-skill patterns, and priority blending strategies.
- The scripts/ directory contains reusable utilities: scripts/AnimationLoader.lua, scripts/TweenHelper.lua, and scripts/IKSetup.lua.
This skill + the roblox-user-interfaces and roblox-vfx skills will let you create motion that feels intentional, responsive, and polished rather than "it moves."
For the latest property or enum behavior, always verify in the Engine API reference: https://create.roblox.com/docs/reference/engine (Animator, AnimationTrack, TweenService, IKControl, etc.).
3D Animations (Rigs, Animator, Tracks, IK, Editor)
Core docs: https://create.roblox.com/docs/animation + editor + inverse-kinematics + events
Rigs and the Animation System
A "rig" is a model whose parts or bones are connected in a hierarchy that the animation system can drive (historically Motor6D joints, now also AnimationConstraint + Bones).
Roblox provides:
- R15 / Rthro standard characters — Rthro uses the R15 skeleton with modified proportions, so it remains compatible with catalog and default animations.
- Rig Builder tool for quick test rigs.
- Custom imported skinned/boned meshes with proper bone hierarchy.
At runtime, every animatable model needs an Animator instance (child of Humanoid for characters, or under an AnimationController for non-Humanoid rigs). The Animator is responsible for loading, playing, blending, and replicating animation state.
Legacy Humanoid:LoadAnimation is deprecated. Always go through the Animator.
Authoring in the Animation Editor
1. Select a rig in the viewport or Explorer. 2. Open Animation Editor (Window > Animation Editor). 3. The interface has:
- Playback controls + name + looping toggle + priority selector.
- Track list (bones/meshes that have keyframes).
- Timeline with scrubber (seconds:frames at 30 fps default; adjustable).
Creating poses:
- Move the scrubber.
- Select a bone or mesh.
- Use Move (or press R for Rotate) to pose it.
- A keyframe is automatically created on that track at the scrubber time.
- Repeat for multiple poses across time.
- Play/scrub to preview. The editor interpolates between keyframes.
Keyframes operations:
- Right-click timeline or keyframes for Add Keyframe, Delete, Duplicate (copy/paste), etc.
- Drag keyframes to retime.
- Right-click keyframe → Easing Style (Linear, Constant/snap, CubicV2, Elastic, Bounce) and Easing Direction (In, Out, InOut).
- Constant style removes interpolation (useful for mechanical or hit reactions).
Optimization:
- Editor auto-removes redundant identical consecutive keyframes in some cases (facial, curve promotion).
- Manual "Optimize Keyframes" tool (⋯ menu) lets you reduce count while previewing.
Looping:
- Toggle the Looping button.
- For seamless loops, duplicate the first keyframe(s) and place them at the end.
Priorities (set in editor or on track at runtime): Core < Idle < Movement < Action < Action2 < Action3 < Action4 (highest). Higher priority animations take precedence in blending.
Events / Markers (critical for gameplay sync):
- Click the settings icon on timeline → Show Animation Events.
- Scrub to desired time, click Edit Animation Events → + Add Event, give it a name (and optional parameter string).
- At runtime:
track:GetMarkerReachedSignal("FootStep"):Connect(function(paramString) ... end) - The parameter is a string you can parse (e.g. "left,heavy" or a JSON-like value).
- Duplicate events across the timeline for recurring actions (multiple footfalls).
Saving vs Exporting/Publishing:
- Save / Save As → stores a KeyframeSequence (or CurveAnimation) locally under the rig in ServerStorage (for iteration). Not replicated.
- Publish to Roblox → makes it a reusable asset with an ID that works in any experience (and group-owned if you choose the creator).
- For default character animation replacement, the final keyframe must be named exactly "End" (case sensitive) before publishing.
Accessing local saves in rare cases: The rig gets an AnimSaves folder with an ObjectValue pointing at the saved data. Do not rely on this for gameplay — publish and use asset IDs.
Runtime Playback (modern)
local Players = game:GetService("Players")
local ContentProvider = game:GetService("ContentProvider")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local player = Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local humanoid = character:WaitForChild("Humanoid")
-- Player characters have an Animator created by the engine on the server. Wait for it so replication works.
-- Custom rigs / NPCs may need fallback creation.
local animator = humanoid:FindFirstChildOfClass("Animator")
or humanoid:WaitForChild("Animator", 1)
if not animator then
animator = Instance.new("Animator")
animator.Parent = humanoid
end
-- Cache the Animation instance; Animator:LoadAnimation caches the returned track
-- when called with the same Animation object on the same Animator.
local anim = ReplicatedStorage:FindFirstChild("MyAnimation") or Instance.new("Animation")
if not anim.AnimationId then
anim.AnimationId = "rbxassetid://1234567890"
anim.Name = "MyAnimation"
anim.Parent = ReplicatedStorage
end
-- Preload asset instances (Animation, Sound, Decal, etc.). AnimationTracks are not valid here.
ContentProvider:PreloadAsync({anim})
local track = animator:LoadAnimation(anim)
track.Priority = Enum.AnimationPriority.Action
track.Looped = false
track:Play(0.1, 1.0, 1.0) -- fadeTime, weight, speed
-- React to markers; store and disconnect connections on cleanup.
local markerConn = track:GetMarkerReachedSignal("Impact"):Connect(function(param)
-- e.g. spawn effect at a specific attachment, apply damage if server-validated, etc.
task.defer(function() ... end)
end)
local endedConn = track.Ended:Connect(function()
-- animation finished moving the rig
end)
local stoppedConn = track.Stopped:Connect(function()
-- cleanup / state tracking
markerConn:Disconnect()
endedConn:Disconnect()
stoppedConn:Disconnect()
end)`KeyframeReached` is an older event; prefer `GetMarkerReachedSignal` for all new work.
Useful track methods/properties:
- Play / Stop / AdjustSpeed / AdjustWeight
- Speed (current), TimePosition, Length, IsPlaying, WeightCurrent/Target
- Priority (can be changed at runtime)
- Stopped, Ended, DidLoop,
GetMarkerReachedSignal(preferred),KeyframeReached(legacy — avoid for new work)
Track caching: Calling Animator:LoadAnimation with the same Animation instance on the same Animator returns the same AnimationTrack object. This is useful for stopping or reusing a track, but be careful about conflicting Play calls with different fade/weight settings.
Async loading with AnimationClipProvider: For previews, streaming, or loading large animations without blocking, use AnimationClipProvider:LoadAnimationClipAsync(animationAssetId) to obtain a temporary AnimationClip, then assign it to an Animation instance's AnimationId or pass it to Animator:LoadAnimation when supported.
Ownership and replication permissions: For a client-played animation on the player's own character to replicate to the server, the animation asset must be owned by the player or by the experience/creator. If the asset is owned by another player or group, the server will not replicate it. NPCs and other characters should have animations played from the server for authority.
Cleanup patterns: Stop tracks and disconnect marker/Ended/Stopped connections on Humanoid.Died or when the character is removed. For LocalScripts, listen to player.CharacterRemoving or the rig's AncestryChanged.
humanoid.Died:Connect(function()
track:Stop(0.1)
markerConn:Disconnect()
endedConn:Disconnect()
stoppedConn:Disconnect()
end)Blending notes: Multiple tracks can be active. The engine blends poses according to priority and current weights. Use weight < 1.0 for partial overlays (e.g. upper body aim while running).
IKControl for Procedural Animation
Add an IKControl under the Humanoid (or AnimationController).
Required properties:
- Type (
Position,Transform,Rotation,LookAt, etc. — Enum.IKControlType) - EndEffector (the Bone or BasePart that should reach the target, e.g. LeftHand)
- Target (any object with a world position — Attachment is convenient for testing)
- ChainRoot (the highest joint in the chain that should be affected, e.g. LeftUpperArm for a full arm reach)
Tuning:
P— higher values make the IK more responsive (can overshoot); lower values are smoother.SmoothTime— how quickly the effector interpolates toward the target; useful for dampening noise.- Use these together to balance snappiness and stability, especially for head tracking or foot planting.
Adding natural limits with Constraints:
- Create a HingeConstraint (or BallSocketConstraint) with the same parent Model as the IKControl.
- Create matching Attachments on the relevant parts (elbow, wrist, etc.).
- Place constraint attachments at the same joint locations represented by the Motor6D C0/C1 offsets, so limits align with the rig's natural pivot.
- For Hinge: rotate the PrimaryAxis attachment to the correct bend axis.
- For BallSocket on wrist: enable LimitsEnabled and set a reasonable UpperAngle (e.g. 80°).
- Test live — you can create/edit IKControls and constraints during a Play session.
IK is excellent for:
- Hand reaching for interactive objects (doors, levers, pickups).
- Head/eyes tracking a point of interest.
- Feet adjusting to uneven terrain or steps (more advanced setups).
Combine with animation tracks: play a "reach" animation at high priority while an IKControl is active, or use markers to enable/disable specific IKControls.
Curve Animations
You can promote a keyframe animation to a curve animation in the editor for per-channel (position/rotation per bone) curve editing. This gives finer artistic control than discrete keyframes.
Performance & Best Practices
- Preload Animation asset instances, not AnimationTracks.
- Cache and reuse Animation instances;
Animator:LoadAnimationcaches tracks when called with the same Animation on the same Animator. - Stop and clean up tracks and connections when the character is removed or dies.
- For replicated characters, understand authority: the client's own character can play animations that replicate via the Animator (subject to asset ownership), while NPCs/other characters are usually server-authoritative.
- Validate gameplay side-effects from markers on the server; use
task.deferinside marker handlers to avoid stalling the animation evaluator. - High numbers of simultaneous complex animations + particles + UI can be expensive — profile.
- Markers are far more efficient and maintainable than polling TimePosition every frame.
Common Pitfalls
- Using the deprecated Humanoid:LoadAnimation path.
- Forgetting to set final keyframe name to "End" when replacing default animations.
- Playing everything at Action priority (or Core) so nothing blends correctly.
- No fade time on Play/Stop (jarring pops).
- Relying on local ServerStorage saves for anything that needs to replicate or persist across sessions.
- IK without constraints (unnatural joint hyperextension).
- Not testing on actual R15 characters vs custom rigs.
- Trusting client markers as authoritative proof of hits; always validate distance, stance, and timing server-side.
- Preloading AnimationTracks instead of Animation asset instances.
For the most current property details or new IKControlType values, always cross-reference the live Engine API reference.
Integration, Events, Priorities, and Cross-System Patterns
Marker-Driven Gameplay (the killer feature)
The single biggest upgrade most developers can make is moving from polling track.TimePosition to using GetMarkerReachedSignal("MarkerName"). Do not use the older `KeyframeReached` event for new work; `GetMarkerReachedSignal` is the modern replacement.
Examples of what you can drive purely from authored markers:
- Footstep sounds + dust ParticleEmitter at the correct foot Attachment.
- Enable/disable hitboxes or damage volumes exactly when the swing "connects".
- Spawn muzzle flash, shell ejection, or impact VFX.
- Trigger camera shake, screen flash, or UI hit markers.
- Start a secondary animation or IKControl (e.g. "grab" a prop at a specific frame).
- Play ability VFX or BillboardGui popups synchronized to the animation.
The parameter string on the marker is free-form text you control in the editor. Many teams use a simple convention like "leftFoot,heavy" or even small JSON strings that the connected function parses.
Inside marker handlers, keep work small and use task.defer for gameplay side-effects to avoid stalling the animation evaluator.
Because markers are part of the published animation asset, designers can retime or add new events without a programmer touching code (as long as the string names stay stable or are versioned).
Priority & Weight Layering
Do not put everything at Action priority.
Typical layering for a character:
- Core (engine defaults, facial, etc.)
- Idle (breathing, subtle shifts)
- Movement (walk, run, strafe — these often blend with each other)
- Action (attacks, emotes, interact, abilities — these usually want to fully or mostly override lower layers)
You can play a low-weight "aim" or "upper body" track at Action priority while a walk/run continues at Movement. Use track:AdjustWeight(0.6) and AdjustSpeed(...) at runtime for fine control (e.g. variable walk speed affecting animation rate).
When a higher priority track starts with a fade time, the engine cross-fades the weights smoothly.
Server vs Client Playback Decisions
- Authority matters: For a player's own character, animations played on the client replicate to the server through the Animator (the asset must be owned by the player or the experience). For NPCs and other players' characters, the server is usually the authority.
- Server playback (recommended for gameplay state): The AnimationTrack runs on the server and state replicates to clients. Good for synchronized attacks, movement abilities, etc. Server can also validate timing via markers or TimePosition if needed.
- Client playback: Purely visual or predictive (cosmetic emotes while moving, client-side reload animations, UI-driven preview animations). Cheaper and more responsive for pure eye candy. The server still needs to know the intent and results via Remotes.
- Ownership/permissions: An animation asset must be owned by the player or the experience/creator for a client-played animation on that player's character to replicate. If ownership is wrong, the server will ignore it.
Many polished games do a hybrid: client plays a predictive animation immediately, server plays the authoritative version and the client corrects/blends if the server disagrees.
Important: Markers are timing hints, not authoritative proof. Any gameplay effect (damage, hit registration) must be validated server-side using distance, stance, timing windows, stamina, etc.
Combining with Other Systems
Particles / VFX (see roblox-vfx skill):
- Best pattern: Animation marker → in the connected function, find an Attachment on the rig (or create a temporary one) and either :Emit() on a pre-placed ParticleEmitter or parent a one-shot emitter.
- You can also tween properties on an emitter (Rate, Speed, etc.) from a marker if you want the effect to ramp up or change character during the animation.
UI (see roblox-user-interfaces skill):
- Markers or track events can start UI tweens (cooldown rings, ability icons lighting up, hit number popups).
- Conversely, a UI button press can start a 3D animation track (with proper server validation for gameplay actions).
- ViewportFrames inside UI can contain their own rigs playing tracks or being driven by IK — this is how many 3D item previews or emote selectors work.
Client-server & security:
- Never trust client animation state for damage, economy, or progression. Use markers or animation completion as a hint, then validate on the server (distance, timing windows, stamina, etc.).
- Markers are not authoritative proof of a hit; always validate stance, distance, and timing before applying gameplay effects.
- Replicate only what is necessary. Full pose data for many players can be expensive.
- Ensure animation assets are owned by the player or experience so client-played player-character animations replicate correctly.
Performance notes:
- Preload everything.
- Limit the number of simultaneously playing high-fidelity tracks + complex particle systems + transparent UI.
- Use the lowest sufficient priority and weight.
- Stop tracks promptly when they are no longer visible or relevant.
Practical Checklist for a New Animated Feature
- [ ] Rig has proper Animator.
- [ ] Animation authored or chosen from catalog with correct priority.
- [ ] Markers added for all gameplay/VFX/UI sync points (with stable names).
- [ ] Preload step in loading sequence.
- [ ] Playback location decided (server for authority, client for cosmetics) and validated where necessary.
- [ ] IK or constraints added if procedural posing is required.
- [ ] Connected marker signals do the minimal work (spawn effect, play sound, start a short tween) and clean up after themselves.
- [ ] Tested with multiple characters and on lower-end devices for frame pacing.
- [ ] Track cleanup on death/remove or when the action is cancelled (disconnect marker/Stopped/Ended connections).
Mastering authored animations + precise marker timing + lightweight UI tweens + targeted IK is what makes Roblox experiences feel "next level" instead of "it moves when I press the button."
UI Tweens and Animation Sequences
Primary source: https://create.roblox.com/docs/ui/animation
Core TweenService Pattern for GuiObjects
local TweenService = game:GetService("TweenService")
local Players = game:GetService("Players")
local playerGui = Players.LocalPlayer:WaitForChild("PlayerGui")
local guiObject = playerGui:WaitForChild("ScreenGui"):WaitForChild("SomeButton") -- or Frame, ImageLabel, etc.
-- Best practice: work in scale + set a sensible AnchorPoint
guiObject.AnchorPoint = Vector2.new(0.5, 0.5)
local tweenInfo = TweenInfo.new(
0.35, -- duration seconds
Enum.EasingStyle.Quad, -- or Sine, Cubic, Back, Bounce, Elastic, Exponential...
Enum.EasingDirection.Out, -- In, Out, InOut
0, -- repeat count ( -1 = infinite )
false, -- reverses
0 -- delay
)
local tween = TweenService:Create(guiObject, tweenInfo, {
Size = UDim2.fromScale(1.15, 1.15),
-- Position = UDim2.fromScale(0.5, 0.5),
-- Rotation = 15,
-- BackgroundTransparency = 0.2,
-- ImageColor3 = Color3.fromRGB(255, 220, 100),
})
tween:Play()Recommended Properties to Tween on Common UI Objects
Frame / CanvasGroup:
- Position, Size, Rotation, BackgroundTransparency, BackgroundColor3
- CanvasGroup.GroupTransparency and GroupColor3 (affects all descendants as a batch — extremely useful)
TextLabel / TextButton:
- The above + TextTransparency, TextColor3
- Avoid TextStrokeTransparency/TextStrokeColor3 for new work; add a UIStroke child and tween its Thickness/Color/Transparency for modern borders.
ImageLabel / ImageButton:
- The Frame properties + ImageTransparency, ImageColor3, ImageRectOffset/ImageRectSize (for sprite sheet tricks)
UIStroke (modern borders on almost anything):
- Color, Thickness, Transparency
Other useful:
- ScrollingFrame.CanvasPosition (smooth programmatic scrolling)
- ViewportFrame + its world contents (you can tween camera CFrame or properties of 3D objects inside the viewport for "3D UI" effects)
Always add UIAspectRatioConstraint when tweening Size on anything that has a designed aspect ratio. This prevents squashing on different screen sizes.
Easing Guidance
- Quad / Cubic — excellent general purpose, natural feel for most UI.
- Sine — gentler.
- Back — slight overshoot then settle (great for "pop" on appear or button press).
- Bounce — playful, use sparingly.
- Elastic — rubber-band feel, can feel over-the-top.
- Constant is an interpolation mode in the Animation Editor / curve animations, not a
TweenServiceeasing style. To snap a value instantly with TweenService, use a 0-duration tween or set the property directly. - Linear only when you truly want constant speed (rare for UI polish).
Experiment in Studio. The visual graphs in the docs are accurate.
Sequences, Chaining, and State Machines
Simple chain:
local grow = TweenService:Create(obj, info, {Size = UDim2.fromScale(1.2, 1.2)})
local shrink = TweenService:Create(obj, info, {Size = UDim2.fromScale(1, 1)})
local conn
grow:Play()
conn = grow.Completed:Connect(function(playbackState)
if playbackState == Enum.PlaybackState.Completed then
conn:Disconnect()
shrink:Play()
end
end)
obj.AncestryChanged:Connect(function(_, parent)
if not parent then
if conn then conn:Disconnect() end
grow:Destroy()
shrink:Destroy()
end
end)For more complex UI flows (open panel → show content → highlight button), maintain a small table of tweens or use a simple state enum + a "playNext" function. Cancel any in-flight tween before starting a conflicting one, and disconnect Completed connections when the sequence finishes or the GUI is destroyed.
Typewriter / Animated Text Reveal
One of the highest-ROI UI animations for immersion.
The official guide provides a complete reusable AnimateUI module using:
LocalizationServicetranslator (optional)utf8.codes/utf8.codepointto iterate Unicode codepoints (use a grapheme-splitting library if you must animate by user-perceived character clusters)- Stripping RichText tags for the animation pass, then restoring or handling separately
TextLabel.MaxVisibleGraphemes = index- Small
task.wait(delayBetweenChars)
Call it from a LocalScript attached to the target TextLabel.
This technique works beautifully combined with sound cues or subtle particle "text dust" on each character.
CanvasGroup Power Moves
Instead of individually tweening 8 elements inside a panel when you want to fade the whole thing: 1. Wrap them (or the important visual children) in a CanvasGroup. 2. Tween only GroupTransparency (0 → 1) and/or GroupColor3. 3. The group blends everything underneath with proper alpha.
This is dramatically cheaper than tweening many individual transparencies and colors.
3D-in-UI via ViewportFrame + Tweens
ViewportFrame lets you embed a miniature 3D scene (with its own ambient lighting, models, and cameras) inside a 2D UI rectangle.
Common pattern:
- Tween the ViewportFrame's size/position/transparency for entrance.
- Inside the viewport, tween the CurrentCamera CFrame, or properties on 3D parts/attachments (or even play AnimationTracks on a rig inside the viewport).
- This is how many inventory inspectors, character previews, and "3D button" effects are achieved.
Important: Real ParticleEmitter, Beam, Trail, and Light objects do not render inside ViewportFrame. Use the viewport's built-in Ambient, LightColor, and LightDirection for lighting.
Performance warning: ViewportFrames have a cost. Limit how many are active and visible, especially on lower-end devices.
Gotchas Specific to UI Tweens
- ClipsDescendants does not clip rotated descendants reliably.
- Tweening very large numbers of transparent UI elements at once is a major source of fill-rate / overdraw problems on mobile.
- Always work in scale (0–1) + AnchorPoint for resolution independence. Hard pixel offsets are a maintenance nightmare.
- UDim2.fromScale vs UDim2.new — prefer the former for clarity when doing relative work.
- RichText tags will break simple character-by-character reveals unless you strip them first (as shown in the official typewriter example).
- Tween objects are GC'd when no longer referenced and finished. Keep a reference only while you need to Cancel/Pause/Resume or listen to Completed.
- Disconnect
Completedconnections and call:Destroy()on tweens when the GUI object is removed to avoid leaking memory. - Style transitions (beta) via the Style Editor are an alternative declarative approach — good for consistent design-system motion.
Multi-Property Tweens
You can (and should) change several properties in one tween for cohesive motion:
TweenService:Create(panel, info, {
Size = UDim2.fromScale(0.9, 0.9),
GroupTransparency = 0, -- if inside CanvasGroup
Position = UDim2.fromScale(0.5, 0.5)
}):Play()When to Prefer AnimationTracks over Tweens in UI Contexts
Rare, but possible: if you have a complex repeating or blended motion that is easier to author once in the Animation Editor and then drive a ViewportFrame rig, or if you want marker events from "UI animation" data.
In 99% of pure 2D GUI cases, TweenService (or the newer style transitions) is the correct, lighter-weight tool.
See the official ui/animation.md page for the complete typewriter module and many more single-property code snippets.
--!strict
local AnimationLoader = {}
AnimationLoader.__index = AnimationLoader
export type AnimationLoader = {
animator: Animator,
cache: { [Animation]: AnimationTrack },
connections: { RBXScriptConnection },
LoadTrack: (self: AnimationLoader, animation: Animation, priority: Enum.AnimationPriority?, fadeTime: number?, weight: number?, speed: number?) -> AnimationTrack,
PlayById: (self: AnimationLoader, assetId: string, priority: Enum.AnimationPriority?, fadeTime: number?, weight: number?, speed: number?) -> AnimationTrack?,
StopAll: (self: AnimationLoader, fadeTime: number?) -> (),
Destroy: (self: AnimationLoader) -> (),
}
local TweenService: TweenService = game:GetService("TweenService")
function AnimationLoader.new(animator: Animator): AnimationLoader
assert(animator and animator:IsA("Animator"), "AnimationLoader requires a valid Animator")
local self = setmetatable({}, AnimationLoader) :: any
self.animator = animator
self.cache = {}
self.connections = {}
return self :: AnimationLoader
end
function AnimationLoader:LoadTrack(
animation: Animation,
priority: Enum.AnimationPriority?,
fadeTime: number?,
weight: number?,
speed: number?
): AnimationTrack
local track = self.cache[animation]
if not track or not track.IsPlaying then
track = self.animator:LoadAnimation(animation)
self.cache[animation] = track
end
track.Priority = priority or Enum.AnimationPriority.Action
track:Play(fadeTime or 0.1, weight or 1, speed or 1)
return track
end
function AnimationLoader:PlayById(
assetId: string,
priority: Enum.AnimationPriority?,
fadeTime: number?,
weight: number?,
speed: number?
): AnimationTrack?
local animation = Instance.new("Animation")
animation.AnimationId = assetId
animation.Name = "Anim_" .. assetId:match("%d+") or "Anim"
local track = self:LoadTrack(animation, priority, fadeTime, weight, speed)
self.connections[#self.connections + 1] = track.Stopped:Connect(function()
if animation then
animation:Destroy()
end
end)
return track
end
function AnimationLoader:StopAll(fadeTime: number?)
for _, track in pairs(self.cache) do
if track.IsPlaying then
track:Stop(fadeTime or 0.1)
end
end
end
function AnimationLoader:Destroy()
for _, conn in ipairs(self.connections) do
conn:Disconnect()
end
table.clear(self.connections)
self:StopAll(0)
table.clear(self.cache)
self.animator = nil :: any
end
return AnimationLoader
--!strict
local IKSetup = {}
export type IKChainConfig = {
parent: Instance,
endEffector: BasePart | Bone,
chainRoot: BasePart | Bone,
target: Attachment | BasePart,
type: Enum.IKControlType?,
smoothTime: number?,
p: number?,
}
function IKSetup.create(config: IKChainConfig): IKControl
local ik = Instance.new("IKControl")
ik.Type = config.type or Enum.IKControlType.Position
ik.EndEffector = config.endEffector
ik.ChainRoot = config.chainRoot
if config.target:IsA("Attachment") then
ik.Target = config.target :: Attachment
else
local targetAttachment = Instance.new("Attachment")
targetAttachment.Name = "IKTarget"
targetAttachment.Parent = config.target :: BasePart
ik.Target = targetAttachment
end
ik.SmoothTime = config.smoothTime or 0.1
ik.P = config.p or 9000
ik.Parent = config.parent
ik.Enabled = true
return ik
end
function IKSetup.createHingeConstraint(parent: BasePart, attachment0: Attachment, attachment1: Attachment, lowerAngle: number?, upperAngle: number?): HingeConstraint
local hinge = Instance.new("HingeConstraint")
hinge.Attachment0 = attachment0
hinge.Attachment1 = attachment1
hinge.LimitsEnabled = true
hinge.LowerAngle = lowerAngle or -90
hinge.UpperAngle = upperAngle or 90
hinge.Restitution = 0
hinge.Parent = parent
return hinge
end
return IKSetup
--!strict
local TweenHelper = {}
export type TweenConfig = {
duration: number,
easingStyle: Enum.EasingStyle?,
easingDirection: Enum.EasingDirection?,
repeatCount: number?,
reverses: boolean?,
delayTime: number?,
}
local TweenService: TweenService = game:GetService("TweenService")
local function buildTweenInfo(config: TweenConfig): TweenInfo
return TweenInfo.new(
config.duration,
config.easingStyle or Enum.EasingStyle.Quad,
config.easingDirection or Enum.EasingDirection.Out,
config.repeatCount or 0,
config.reverses or false,
config.delayTime or 0
)
end
function TweenHelper.tween(instance: Instance, properties: { [string]: any }, config: TweenConfig): Tween
local info = buildTweenInfo(config)
local tween = TweenService:Create(instance, info, properties)
tween:Play()
return tween
end
function TweenHelper.tweenAsync(instance: Instance, properties: { [string]: any }, config: TweenConfig): boolean
local tween = TweenHelper.tween(instance, properties, config)
local completed = false
local conn: RBXScriptConnection?
conn = tween.Completed:Connect(function()
completed = true
if conn then
conn:Disconnect()
end
end)
while not completed do
task.wait()
end
return completed
end
function TweenHelper.sequence(steps: { { instance: Instance, properties: { [string]: any }, config: TweenConfig } }): ()
local current = 1
local function playNext()
if current > #steps then
return
end
local step = steps[current]
current += 1
local tween = TweenHelper.tween(step.instance, step.properties, step.config)
local conn: RBXScriptConnection?
conn = tween.Completed:Once(function()
if conn then
conn:Disconnect()
end
playNext()
end)
end
playNext()
end
return TweenHelper