Playing Built-in Animations
Load and play the built-in rooomAvatars animations on your avatar model in three.js or Babylon.js.
By the end of this tutorial, a built-in animation from the rooomAvatars API plays on your avatar model in either three.js or Babylon.js.
Prerequisites
- A rooomAvatars API token — see Authentication
- An avatar ID from a completed configurator session — see the Editor embedding guide
- A web project using either three.js or Babylon.js
How it works
Avatar models (/model/:id.glb) and built-in animations (/animations/:gender/:name.glb) share a Mixamo-compatible skeleton. The animation clip references bones by name, and the same bone names exist on the avatar, so any clip can play on any avatar of the matching gender.
You have two ways to get animations onto your avatar:
- Bake them into the model download — pass the
animationsquery parameter onGET /model/:id.glb(e.g.?animations=locomotionor?animations=all). The animations ship inside the same GLB as the avatar — simplest if you always need the same clips. - Download animations separately (this tutorial) — request the avatar without baked animations (the default) and load each animation GLB on demand. Keeps the initial download small and lets you swap clips at runtime without re-downloading the avatar.
INFO
Always download the animation matching your avatar's gender (masculine or feminine). The skeletons differ slightly in proportions and joint orientation — mixing them produces visible distortion.
Step 1: List available animations
Call GET /animations to discover which animation names ship with rooomAvatars:
curl "ANIMATIONS_URL"The response is grouped by gender:
{
"status": "ok",
"data": {
"masculine": ["locomotion_idle", "locomotion_walk", "locomotion_run"],
"feminine": ["locomotion_idle", "locomotion_walk", "locomotion_run"]
}
}Step 2: Play the animation
Pick one of the engine examples below.
three.js
Since glTF animation tracks reference bones by name and the names match, no explicit retargeting is required — create an AnimationMixer on the avatar and play the clip from the animation file.
import * as THREE from "three";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
const loader = new GLTFLoader();
const [avatarGltf, animGltf] = await Promise.all([
loader.loadAsync("https://HOST/model/AVATAR_ID.glb"),
loader.loadAsync("https://HOST/animations/masculine/locomotion_idle.glb"),
]);
scene.add(avatarGltf.scene);
const mixer = new THREE.AnimationMixer(avatarGltf.scene);
mixer.clipAction(animGltf.animations[0]).play();
// Drive the mixer from your render loop
const clock = new THREE.Clock();
renderer.setAnimationLoop(() => {
mixer.update(clock.getDelta());
renderer.render(scene, camera);
});Babylon.js
Babylon attaches AnimationGroups to the nodes they were loaded with. To play an animation from a separate GLB on the avatar, load it into an AssetContainer and clone the group with a target converter that maps each source node to the avatar's node or bone by name.
import { SceneLoader } from "@babylonjs/core";
import "@babylonjs/loaders/glTF";
// Load the avatar into the scene
await SceneLoader.ImportMeshAsync("", "https://HOST/", "model/AVATAR_ID.glb", scene);
// Load the animation isolated in an AssetContainer (not merged into the scene)
const animContainer = await SceneLoader.LoadAssetContainerAsync(
"https://HOST/",
"animations/masculine/locomotion_idle.glb",
scene,
);
// Retarget the clip onto the avatar's nodes/bones by matching names
const idle = animContainer.animationGroups[0].clone("idle", (source) =>
scene.getTransformNodeByName(source.name) ?? scene.getBoneByName(source.name),
);
// Dispose the animation's source meshes/skeleton; the retargeted group survives
animContainer.dispose();
idle.play(true);Next steps
- Switch between clips by loading multiple animation GLBs and calling
play()on the matching mixer action /AnimationGroup. - For custom motion from Mixamo or other sources, see Using Mixamo Animations.