3D models in .glTF and .glb format can include as many animations as you want in them. Animations tell the mesh how to move, by specifying a series of keyframes that are laid out over time, the mesh then blends from one pose to the other to simulate continuous movement.
Most 3D model animations are skeletal animations. These animations simplify the complex geometry of the model into a "stick figure", linking every vertex in the mesh to the closest bone in the skeleton. Modelers adjust the skeleton into different poses, and the mesh stretches and bends to follow these movements.
As an alternative, vertex animations animate a model without the need of a skeleton. These animations specify the position of each vertex in the model directly. Decentraland supports these animations as well.
See Animations for details on how to create animations for a 3D model. Read Shape components for instructions on how to import a 3D model to a scene.
Tip: Animations are usually better for moving something in place, not for changing the position of an entity. For example, you can set an animation to move a character's feet in place, but to change the location of the entity it's best to use the Transform component. See Positioning entities for more details.
Not all glTF files include animations. To see if there are any available, you can do the following:
Tip: In skeletal animations, an animation name is often comprised of its armature name, an underscore and its animation name. For example
myArmature_animation1
.
If a 3d model includes any animations, the default behavior is that the first of these is always played on a loop.
To avoid this behavior, add an Animator
component to the entity that has the model, and then handle the playing of animations explicitly. If an Animator
component is present in the entity, all animations default to a stopped
state, and need to be manually played.
An Animator
component is used to access all the animations of the entity and can be used to explicitly tell the entity to play or stop an animation. Each animation is handled by an AnimationState
object.
// Create entitylet shark = new Entity()// Add a 3D model to itshark.addComponent(new GLTFShape("models/shark.gltf"))// Create animator componentlet animator = new Animator()// Add animator component to the entityshark.addComponent(animator)// Instance animation clip objectconst clipSwim = new AnimationState("swim")// Add animation clip to Animator componentanimator.addClip(clipSwim)// Add entity to engineengine.addEntity(shark)COPY CODE
You can also achieve the same with less statements:
// Create and add animator componentshark.addComponent(new Animator())// Instance and add a clipshark.getComponent(Animator).addClip(new AnimationState("swim"))COPY CODE
You can retrieve an AnimationState
object from an Animator
component with the getClip()
function.
// Create and get a cliplet clipSwim = animator.getClip("swim")COPY CODE
The AnimationState
object doesn't store the actual transformations that go into the animation, that's all in the .glTF file. Instead, the AnimationState
object has a state that keeps track how far it has advanced along the animation.
If you don't have a pointer to refer to the clip object directly, you can fetch a clip from the Animator
by name using getClip()
.
// Create and add a clipshark.getComponent(Animator).addClip(new AnimationState("swim"))// Fetch the clipshark.getComponent(Animator).getClip("swim")COPY CODE
When an AnimationState
is created, it starts as paused by default.
The simplest way to play or pause it is to use the play()
and pause()
methods of the AnimationState
.
// Create animation clipconst clipSwim = new AnimationState("swim")// Start playing the clipclipSwim.play()// Pause the playing of the clipclipSwim.pause()COPY CODE
The play()
function on an AnimationState
object has one optional parameter:
reset
: If true, it always plays the animation from the start. Default: false.clipSwim.play(true)COPY CODE
The following table summarizes how play()
behaves, using different values for the reset
property:
reset = false (default) | reset = true | |
---|---|---|
Currently playing | Has no effect. | Plays from the start. |
Paused | Resumes from last frame played. | Plays from the start. |
Finished (Non-looping) | Plays from the start. | Plays from the start. |
You can also play an animation from the Animator
component of an entity.
shark.getComponent(Animator).play(clipSwim)COPY CODE
When calling the play()
function on the Animator component, there are two parameters to set:
clip
: An AnimationState object to playreset
:(optional) If true, it always plays the animation plays from the start. Default: false.By default, animations are played in a loop that keeps repeating the animation forever.
Change this setting by setting the looping
property in the AnimationState
object.
// Create animation clipconst biteClip = new AnimationState("bite")// Set loop to falsebiteClip.looping = false// Start playing the clipbiteClip.play()COPY CODE
If looping
is set to false, the animation plays just once and then stops.
When an animation finishes playing or is paused, the 3D model remains in the last posture it had.
To stop an animation and set the posture back to the first frame in the animation, use the stop()
function of the AnimationState
object.
clipSwim.stop()COPY CODE
To play an animation from the start, regardless of what frame the animation is currently in, set the reset
property on the play()
function to true.
clipSwim.play(true)COPY CODE
Note: Resetting the posture is an abrupt change. If you want to make the model transition smoothly tinto another posture, you can either:
- apply an animation with a `weight` property of 0 and gradually increase the `weight`- create an animation clip that describes a movement from the posture you want to transition from to the default posture you want.COPY CODE
If a 3D model has multiple animations packed into it, a single Animator
component can deal with all of them.
Animations exist in layers in an Animator
component. If two animations are in the same layer, only one of them can play at a time. Starting one will stop the other. If two animations exist on separate layers, they can play at the same time, given that their weight values add up, or if they each control different bones or vertexes from the model.
let shark = new Entity()shark.addComponent(new GLTFShape("models/shark.gltf"))// Create animator componentlet animator = new Animator()// Add animator component to the entityshark.addComponent(animator)// Create animation state objectsconst clipSwim = new AnimationState("swim", { layer: 0 })const biteClip = new AnimationState("bite", { layer: 1 })// Add animation state objects to the Animator componentshark.getComponent(Animator).addClip(clipSwim)shark.getComponent(Animator).addClip(biteClip)clipSwim.play()biteClip.play()engine.addEntity(shark)COPY CODE
In the example above, two animations are handled by separate AnimationState
objects, and they are then both assigned to the same Animator
component.
Note: If the layer of an animation isn't specified, it's assigned to layer 0.
Each bone in an animation can only be affected by one animation at a time, unless these animations have a weight
that adds up to a value of 1 or less.
If one animation only affects a character's legs, and another only affects a character's head, then they can be played at the same time without any issue. But if they both affect the character's legs, then you must either only play one at a time, or play them with lower weight
values.
If in the above example, the bite
animation only affects the shark's mouth, and the swim
animation only affects the bones of the shark's spine, then they can both be played at the same time if they're on separate layers.
Change the speed at which an animation is played by changing the speed
property. The value of the speed is 1 by default.
// Create animation clipconst clipSwim = new AnimationState("swim")// Set speed to twice as fastclipSwim.speed = 2// Start playing the clipclipSwim.play()COPY CODE
Set the speed lower than 1 to play it slower, for example to 0.5 to play it at half the speed. Set it higher than 1 to play it faster, for example to 2 to play it at double the speed.
The weight
property allows a single model to carry out multiple animations on different layers at once, calculating a weighted average of all the movements involved in the animation. The value of weight
determines how much importance that animation will be given in the average.
By default, weight
is equal to 1, it can't be any higher than 1.
// Create animation clipconst clipSwim = new AnimationState("swim")// Set weightclipSwim.weight = 0.5// Start playing the clipclipSwim.play()COPY CODE
The weight
value of all active animations in an entity should add up to 1 at all times. If it adds up to less than 1, the weighted average will be using the default position of the armature for the remaining part of the calculation.
const clipSwim = new AnimationState("swim")clipSwim.weight = 0.2animator.addClip(clipSwim)clipSwim.play()COPY CODE
For example, in the code example above, we're playing the swim animation, that only has a weight
of 0.2. This swimming movement will be quite subtle: only 20% of the intensity that the animation defines. The remaining 80% of the calculation takes values from the default posture of the armature.
The weight
property can be used in interesting ways, for example the weight
property of swim could be set in proportion to how fast the shark is swimming, so you don't need to create multiple animations for fast and slow swimming.
You could also change the weight
value gradually when starting and stopping an animation to give it a more natural transition and to avoid jumps from the default pose to the first pose in the animation.
Note: The added
weight
value of all animations that are acting on a 3D model's bone can't be more than 1. If more than one animation is affecting the same bones at the same time, they need to have their weight set to values that add to less than 1.
Use the setParams()
function of the AnimationState
object to set multiple parameters at once.
You can configure the following parameters:
looping
: Boolean to determine if the animation is played in a continuous loop.speed
: A number that determines how fast the animation is played.layer
: The layer of the animation. To play multiple animations at once, they must be on separate layers in the Animator
component. By default, animations are added to layer 0.weight
: Used to blend animations using weighted average.const clipSwim = new AnimationState("swim")clipSwim.setParams({looping: true,speed: 2,layer: 1,weight: 0.5,})COPY CODE
You can use a same instance of a GLTFShape
component on multiple entities to save resources. If each entity has both its own Animator
component and its own AnimationState
objects, then they can each be animated independently.
//create entitieslet shark1 = new Entity()let shark2 = new Entity()// create reusable shape componentlet sharkShape = new GLTFShape("models/shark.gltf")// Add the same GLTFShape instance to both entitiesshark1.addComponent(sharkShape)shark2.addComponent(sharkShape)// Create separate animator componentslet animator1 = new Animator()let animator2 = new Animator()// Add separate animator components to the entitiesshark1.addComponent(animator1)shark2.addComponent(animator2)// Instance separate animation clip objectsconst clipSwim1 = new AnimationState("swim")const clipSwim2 = new AnimationState("swim")// Add animation clips to Animator componentsanimator1.addClip(clipSwim1)animator2.addClip(clipSwim2)engine.addEntity(shark1)engine.addEntity(shark2)COPY CODE
Note: If you define a single
AnimationState
object instance and add it to multipleAnimator
components from different entities, all entities using theAnimationState
instance will be animated together at the same time.