Easiest way to play skeletal animation from glTF

hey everyone,

whats the simplest way to play the animations of an imported GLTF file? The file contains a mesh, its skeleton and two animation clips (“jump”, “wink”). I test it, the file works fine (tested in two GLTF Viewers) but when i try to play the animations:

scene.getObjectByName(“Suzanne”).geometry.animation[0].play()

i get an error, that there is no property like this…

1 Like

Do it like so:

const loader = new THREE.GLTFLoader();
loader.load( 'model.glb', function ( gltf ) {

	mixer = new THREE.AnimationMixer( gltf.scene );
	var action = mixer.clipAction( gltf.animations[ 0 ] );
	action.play();

	scene.add( gltf.scene );

} );

This code is deprecated. Animations are not stored in the geometry object. Besides, do not forget to update the mixer in your animation loop.

1 Like

thanks for your help.

is there a seperate loop fot the bone animation?

1 Like

No it’s your normal animation loop, see

1 Like

that works, thank you. Its also possible to manipulate the playback speed, very interesting :slight_smile:

My glb file has more than one animation clip but in the loader only one got addet to the mixer object
var action = mixer.clipAction( gltf.animations[ 0 ] );

so i tried to add another clip in the loader callback

let cube
const loader = new THREE.GLTFLoader()
loader.load( ‘assets/cube_bone.glb’, (gltf) => {
mixer = new THREE.AnimationMixer( gltf.scene );
let action = mixer.clipAction( gltf.animations[ 1 ] )
mixer.clipAction( gltf.animations[ 0 ] )
action.play();
scene.add( gltf.scene )
} );

so, the mixer objects now contains two action, but i couldnt start the second clip while my app was running
mixer._actions[1].play()

1 Like

Yes, for example by modulating AnimationAction.timeScale.

If you want to playback more than one animation clip, I suggest you study how webgl_animation_skinning_blending works. The example creates the animation actions like so:

mixer = new THREE.AnimationMixer( model );

idleAction = mixer.clipAction( animations[ 0 ] );
walkAction = mixer.clipAction( animations[ 3 ] );
runAction = mixer.clipAction( animations[ 1 ] );

At a later point, it depends on your requirements how you want to playback these action (e.g. with or without fading).

1 Like

thanks again. I think i now better understand how the animation system is working :slight_smile:

here’s my working example if some else is searching for it…

let camera, scene, renderer, dt, lastframe = Date.now(), mixer

let idleAction, jumpAction

init();
update();

function init() {

camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.01, 500 );
camera.position.z = 9
camera.position.y = 4

scene = new THREE.Scene()

let ambilight = new THREE.AmbientLight( 0xcccffc, 3.4 );
ambilight.name = "ambilight"
scene.add( ambilight );

let dirlight = new THREE.DirectionalLight( 0xffcffc, 5.4 );
dirlight.name = "dirlight"
dirlight.position.set(0,0,0)
scene.add( dirlight );

const loader = new THREE.GLTFLoader()
loader.load( 'assets/cube_bone.glb', (gltf)  => {
    mixer = new THREE.AnimationMixer( gltf.scene );
    idleAction = mixer.clipAction( gltf.animations[ 1 ] )
    jumpAction =  mixer.clipAction( gltf.animations[ 0 ] )
    scene.add( gltf.scene )
    // starts idle animation 
    idleAction.play()
} );


renderer = new THREE.WebGLRenderer( { antialias: true } )
renderer.setClearColor("#434")
renderer.physicallyCorrectLights = true;
renderer.setSize( window.innerWidth, window.innerHeight )
window.addEventListener("resize", e => {
    renderer.setSize( window.innerWidth, window.innerHeight )
    camera.aspect =  window.innerWidth/ window.innerHeight
    camera.updateProjectionMatrix() 
})
document.body.appendChild( renderer.domElement );

}

function update() {
dt = (Date.now()-lastframe)/1000

if(mixer){
    mixer.update(dt)        
}

renderer.render( scene, camera );
lastframe=Date.now()
requestAnimationFrame( update );

}

2 Likes

This guide might be helpful: three.js docs

2 Likes