animations-tweens
Animations and Tweens in Decentraland
When to Use Which Animation Approach
| Need | Approach | When |
|---|---|---|
| Play animation baked into a .glb model | Animator |
Character walks, door opens, flag waves — any animation created in Blender/Maya |
| Move/rotate/scale an entity smoothly | Tween |
Sliding doors, floating platforms, growing objects — procedural A-to-B motion |
| Chain multiple animations in sequence | TweenSequence |
Patrol paths, multi-step doors, complex choreography |
| Continuous per-frame control | engine.addSystem() |
Physics-like motion, following a target, custom easing |
Decision flow:
- Does the .glb model already have the animation? →
Animator - Is it a simple move/rotate/scale between two values? →
Tween - Do you need frame-by-frame control or custom math? → System with
dt
GLTF Animations (Animator)
Play animations embedded in .glb models:
import { engine, Transform, GltfContainer, Animator } from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'
const character = engine.addEntity()
Transform.create(character, { position: Vector3.create(8, 0, 8) })
GltfContainer.create(character, { src: 'models/character.glb' })
// Set up animation states
Animator.create(character, {
states: [
{ clip: 'idle', playing: true, loop: true, speed: 1 },
{ clip: 'walk', playing: false, loop: true, speed: 1 },
{ clip: 'attack', playing: false, loop: false, speed: 1.5 }
]
})
// Play a specific animation
Animator.playSingleAnimation(character, 'walk')
// Stop all animations
Animator.stopAllAnimations(character)
Switching Animations
function playAnimation(entity: Entity, clipName: string) {
const animator = Animator.getMutable(entity)
// Stop all
for (const state of animator.states) {
state.playing = false
}
// Play the desired one
const state = animator.states.find(s => s.clip === clipName)
if (state) {
state.playing = true
}
}
Tweens (Code-Based Animation)
Animate entity properties smoothly over time:
Move
import { engine, Transform, Tween, EasingFunction } from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'
const box = engine.addEntity()
Transform.create(box, { position: Vector3.create(2, 1, 8) })
Tween.create(box, {
mode: Tween.Mode.Move({
start: Vector3.create(2, 1, 8),
end: Vector3.create(14, 1, 8)
}),
duration: 2000, // milliseconds
easingFunction: EasingFunction.EF_EASESINE
})
Rotate
Tween.create(box, {
mode: Tween.Mode.Rotate({
start: Quaternion.fromEulerDegrees(0, 0, 0),
end: Quaternion.fromEulerDegrees(0, 360, 0)
}),
duration: 3000,
easingFunction: EasingFunction.EF_LINEAR
})
Scale
Tween.create(box, {
mode: Tween.Mode.Scale({
start: Vector3.create(1, 1, 1),
end: Vector3.create(2, 2, 2)
}),
duration: 1000,
easingFunction: EasingFunction.EF_EASEOUTBOUNCE
})
Tween Sequences (Chained Animations)
Chain multiple tweens to play one after another:
import { TweenSequence, TweenLoop } from '@dcl/sdk/ecs'
// First tween
Tween.create(box, {
mode: Tween.Mode.Move({
start: Vector3.create(2, 1, 8),
end: Vector3.create(14, 1, 8)
}),
duration: 2000,
easingFunction: EasingFunction.EF_EASESINE
})
// Chain sequence
TweenSequence.create(box, {
sequence: [
// Second: move back
{
mode: Tween.Mode.Move({
start: Vector3.create(14, 1, 8),
end: Vector3.create(2, 1, 8)
}),
duration: 2000,
easingFunction: EasingFunction.EF_EASESINE
}
],
loop: TweenLoop.TL_RESTART // Loop the entire sequence
})
Easing Functions
Available easing functions from EasingFunction:
EF_LINEAR— Constant speedEF_EASEINQUAD/EF_EASEOUTQUAD/EF_EASEQUAD— QuadraticEF_EASEINSINE/EF_EASEOUTSINE/EF_EASESINE— Sinusoidal (smooth)EF_EASEINEXPO/EF_EASEOUTEXPO/EF_EASEEXPO— ExponentialEF_EASEINELASTIC/EF_EASEOUTELASTIC/EF_EASEELASTIC— Elastic bounceEF_EASEOUTBOUNCE/EF_EASEINBOUNCE/EF_EASEBOUNCE— Bounce effectEF_EASEINBACK/EF_EASEOUTBACK/EF_EASEBACK— OvershootEF_EASEINCUBIC/EF_EASEOUTCUBIC/EF_EASECUBIC— CubicEF_EASEINQUART/EF_EASEOUTQUART/EF_EASEQUART— QuarticEF_EASEINQUINT/EF_EASEOUTQUINT/EF_EASEQUINT— QuinticEF_EASEINCIRC/EF_EASEOUTCIRC/EF_EASECIRC— Circular
Custom Animation Systems
For complex animations, create a system:
// Continuous rotation system
function spinSystem(dt: number) {
for (const [entity] of engine.getEntitiesWith(Transform, Spinner)) {
const transform = Transform.getMutable(entity)
const spinner = Spinner.get(entity)
// Rotate around Y axis
const currentRotation = Quaternion.toEulerAngles(transform.rotation)
transform.rotation = Quaternion.fromEulerDegrees(
currentRotation.x,
currentRotation.y + spinner.speed * dt,
currentRotation.z
)
}
}
engine.addSystem(spinSystem)
Tween Helper Methods
Use shorthand helpers that create or replace the Tween component directly on the entity:
import { Tween, EasingFunction } from '@dcl/sdk/ecs'
// Move — signature: Tween.setMove(entity, start, end, duration, easingFunction?)
Tween.setMove(entity,
Vector3.create(0, 1, 0), Vector3.create(0, 3, 0),
1500, EasingFunction.EF_EASEINBOUNCE
)
// Rotate — signature: Tween.setRotate(entity, start, end, duration, easingFunction?)
Tween.setRotate(entity,
Quaternion.fromEulerDegrees(0, 0, 0), Quaternion.fromEulerDegrees(0, 180, 0),
2000, EasingFunction.EF_EASEOUTQUAD
)
// Scale — signature: Tween.setScale(entity, start, end, duration, easingFunction?)
Tween.setScale(entity,
Vector3.One(), Vector3.create(2, 2, 2),
1000, EasingFunction.EF_LINEAR
)
Yoyo Loop Mode
TL_YOYO reverses the tween at each end instead of restarting:
TweenSequence.create(entity, {
sequence: [{ duration: 1000, ... }],
loop: TweenLoop.TL_YOYO
})
Detecting Tween Completion
Use tweenSystem.tweenCompleted() to check if a tween finished this frame:
engine.addSystem(() => {
if (tweenSystem.tweenCompleted(entity)) {
console.log('Tween finished on', entity)
}
})
Animator Extras
Additional Animator features:
// Get a specific clip to modify
const clip = Animator.getClip(entity, 'Walk')
// shouldReset: restart animation from beginning when re-triggered
Animator.playSingleAnimation(entity, 'Attack', true) // resets to start
// weight: blend between animations (0.0 to 1.0)
const anim = Animator.getMutable(entity)
anim.states[0].weight = 0.5 // blend walk at 50%
anim.states[1].weight = 0.5 // blend idle at 50%
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
| GLTF animation not playing | Wrong clip name in Animator.states |
Open the .glb in a viewer (e.g., Blender) to find exact clip names — they are case-sensitive |
| Animator component has no effect | Entity missing GltfContainer |
Animator only works on entities that have a loaded GLTF model |
| Tween doesn't move | Start and end positions are the same | Verify start and end values differ in Tween.Mode.Move() |
| Tween plays once then stops | No TweenSequence with loop |
Add TweenSequence.create(entity, { sequence: [], loop: TweenLoop.TL_YOYO }) for back-and-forth |
| Animation jitters or stutters | Creating new Tween every frame | Only create Tween once, not inside a system — use tweenSystem.tweenCompleted() to chain |
Need 3D models to animate? See the add-3d-models skill for loading GLTF models that contain animation clips.
Best Practices
- Use Tweens for simple A-to-B animations (doors, platforms, UI elements)
- Use Animator for character/model animations baked into GLTF files
- Use Systems for continuous or physics-based animations
- Tween durations are in milliseconds (1000 = 1 second)
- Combine move + rotate tweens by applying them to parent/child entities
- For looping: use
TweenSequencewithloop: TweenLoop.TL_RESTART
More from dcl-regenesislabs/opendcl
optimize-scene
Optimize Decentraland scene performance. Scene limit formulas (triangles, entities, materials, textures, height per parcel count), object pooling, LOD patterns, texture optimization, system throttling, and asset preloading. Use when the user wants to optimize, improve performance, fix lag, reduce load time, check limits, or reduce entity/triangle count. Do NOT use for deployment (see deploy-scene).
51game-design
Plan and design Decentraland games and interactive experiences. Scene limit formulas, performance budgets, texture requirements, asset preloading, state management patterns (module-level, component-based, state machines), object pooling, UX/UI guidelines, input design, and MVP planning. Use when the user wants game design advice, scene architecture, performance planning, or help structuring a game. Do NOT use for specific implementation (see add-interactivity, build-ui, multiplayer-sync).
30audio-video
Add sound effects, music, audio streaming, and video players to Decentraland scenes. Covers AudioSource (local files), AudioStream (streaming URLs), VideoPlayer (video surfaces), video events, and media permissions. Use when the user wants sound, music, audio, video screens, radio, or media playback. Do NOT use for 3D model animations (see animations-tweens).
30add-3d-models
Add 3D models (.glb/.gltf) to a Decentraland scene using GltfContainer. Covers loading, positioning, scaling, colliders, parenting, and browsing 5,700+ free assets from the OpenDCL catalog. Use when the user wants to add models, import GLB files, find free 3D assets, or set up model colliders. Do NOT use for materials/textures (see advanced-rendering) or model animations (see animations-tweens).
28nft-blockchain
NFT display and blockchain interaction in Decentraland. NftShape (framed NFT artwork), wallet checks (getPlayer, isGuest), signedFetch (authenticated requests), smart contract interaction (eth-connect, createEthereumProvider), and RPC calls. Use when the user wants NFTs, blockchain, wallet, smart contracts, Web3, crypto, or token gating. Do NOT use for player avatar data or emotes (see player-avatar).
27build-ui
Build 2D screen-space UI for Decentraland scenes using React-ECS (JSX). Create HUDs, menus, health bars, scoreboards, dialogs, buttons, inputs, and dropdowns. Use when the user wants screen overlays, on-screen UI, HUD elements, menus, or form inputs. Do NOT use for 3D in-world text (see advanced-rendering) or clickable 3D objects (see add-interactivity).
26